1use super::{ExportError, ExportResult};
7use crate::models::{Column, DataModel, Table};
8use serde_yaml;
9use std::collections::HashMap;
10
11fn get_physical_type(column: &Column) -> String {
14 column
15 .physical_type
16 .clone()
17 .unwrap_or_else(|| column.data_type.clone())
18}
19
20pub struct ODCSExporter;
22
23impl ODCSExporter {
24 pub fn export_table(table: &Table, _format: &str) -> String {
53 Self::export_odcs_v3_1_0_format(table)
55 }
56
57 fn parse_struct_properties_from_data_type(
60 parent_name: &str,
61 data_type: &str,
62 map_data_type_fn: &dyn Fn(&str) -> (String, bool),
63 ) -> Option<Vec<serde_yaml::Value>> {
64 use crate::import::odcs::ODCSImporter;
65
66 let importer = ODCSImporter::new();
67 let field_data = serde_json::Map::new();
68
69 let struct_type = if data_type.to_uppercase().starts_with("ARRAY<STRUCT<") {
71 if let Some(start) = data_type.find("STRUCT<") {
73 &data_type[start..]
74 } else {
75 data_type
76 }
77 } else {
78 data_type
79 };
80
81 if let Ok(nested_cols) =
83 importer.parse_struct_type_from_string(parent_name, struct_type, &field_data)
84 && !nested_cols.is_empty()
85 {
86 use std::collections::HashMap;
90 let mut props_map: HashMap<String, Vec<&crate::models::Column>> = HashMap::new();
91
92 for nested_col in &nested_cols {
93 let name_after_prefix = nested_col
95 .name
96 .strip_prefix(&format!("{}.[]", parent_name))
97 .or_else(|| nested_col.name.strip_prefix(&format!("{}.", parent_name)))
98 .unwrap_or(&nested_col.name);
99
100 let immediate_child = name_after_prefix
102 .split('.')
103 .next()
104 .unwrap_or(name_after_prefix)
105 .to_string();
106
107 props_map
108 .entry(immediate_child)
109 .or_default()
110 .push(nested_col);
111 }
112
113 let mut props_array = Vec::new();
115 for (immediate_child, child_cols) in props_map {
116 if immediate_child.is_empty() {
118 continue;
119 }
120
121 let has_nested_struct = child_cols.iter().any(|col| {
123 let name_after_prefix = col
124 .name
125 .strip_prefix(&format!("{}.[]", parent_name))
126 .or_else(|| col.name.strip_prefix(&format!("{}.", parent_name)))
127 .unwrap_or(&col.name);
128 name_after_prefix.contains('.')
129 });
130
131 if has_nested_struct {
132 let mut prop_map = serde_yaml::Mapping::new();
134 let id = immediate_child
135 .chars()
136 .map(|c| {
137 if c.is_alphanumeric() {
138 c.to_lowercase().to_string()
139 } else {
140 "_".to_string()
141 }
142 })
143 .collect::<String>()
144 .replace("__", "_");
145
146 prop_map.insert(
147 serde_yaml::Value::String("id".to_string()),
148 serde_yaml::Value::String(format!("{}_field", id)),
149 );
150 prop_map.insert(
151 serde_yaml::Value::String("name".to_string()),
152 serde_yaml::Value::String(immediate_child.clone()),
153 );
154 prop_map.insert(
155 serde_yaml::Value::String("logicalType".to_string()),
156 serde_yaml::Value::String("object".to_string()),
157 );
158
159 let mut nested_props = Vec::new();
161 for nested_col in child_cols {
162 let name_after_prefix = nested_col
164 .name
165 .strip_prefix(&format!("{}.[]", parent_name))
166 .or_else(|| nested_col.name.strip_prefix(&format!("{}.", parent_name)))
167 .unwrap_or(&nested_col.name);
168
169 let nested_name = name_after_prefix
170 .strip_prefix(&format!("{}.", immediate_child))
171 .unwrap_or(name_after_prefix)
172 .to_string();
173
174 if !nested_name.is_empty() && nested_name != immediate_child {
175 let (logical_type, _) = map_data_type_fn(&nested_col.data_type);
176 let nested_id = nested_name
177 .chars()
178 .map(|c| {
179 if c.is_alphanumeric() {
180 c.to_lowercase().to_string()
181 } else {
182 "_".to_string()
183 }
184 })
185 .collect::<String>()
186 .replace("__", "_");
187
188 let mut nested_prop = serde_yaml::Mapping::new();
189 nested_prop.insert(
190 serde_yaml::Value::String("id".to_string()),
191 serde_yaml::Value::String(format!("{}_field", nested_id)),
192 );
193 nested_prop.insert(
194 serde_yaml::Value::String("name".to_string()),
195 serde_yaml::Value::String(nested_name),
196 );
197 nested_prop.insert(
198 serde_yaml::Value::String("logicalType".to_string()),
199 serde_yaml::Value::String(logical_type),
200 );
201 nested_prop.insert(
202 serde_yaml::Value::String("physicalType".to_string()),
203 serde_yaml::Value::String(get_physical_type(nested_col)),
204 );
205
206 if !nested_col.nullable {
207 nested_prop.insert(
208 serde_yaml::Value::String("required".to_string()),
209 serde_yaml::Value::Bool(true),
210 );
211 }
212
213 nested_props.push(serde_yaml::Value::Mapping(nested_prop));
214 }
215 }
216
217 if !nested_props.is_empty() {
218 prop_map.insert(
219 serde_yaml::Value::String("properties".to_string()),
220 serde_yaml::Value::Sequence(nested_props),
221 );
222 }
223
224 props_array.push(serde_yaml::Value::Mapping(prop_map));
225 } else {
226 let nested_col = child_cols[0];
228 let mut prop_map = serde_yaml::Mapping::new();
229 let (logical_type, _) = map_data_type_fn(&nested_col.data_type);
230
231 let id = immediate_child
232 .chars()
233 .map(|c| {
234 if c.is_alphanumeric() {
235 c.to_lowercase().to_string()
236 } else {
237 "_".to_string()
238 }
239 })
240 .collect::<String>()
241 .replace("__", "_");
242
243 prop_map.insert(
244 serde_yaml::Value::String("id".to_string()),
245 serde_yaml::Value::String(format!("{}_field", id)),
246 );
247 prop_map.insert(
248 serde_yaml::Value::String("name".to_string()),
249 serde_yaml::Value::String(immediate_child),
250 );
251 prop_map.insert(
252 serde_yaml::Value::String("logicalType".to_string()),
253 serde_yaml::Value::String(logical_type),
254 );
255 prop_map.insert(
256 serde_yaml::Value::String("physicalType".to_string()),
257 serde_yaml::Value::String(get_physical_type(nested_col)),
258 );
259
260 if !nested_col.nullable {
261 prop_map.insert(
262 serde_yaml::Value::String("required".to_string()),
263 serde_yaml::Value::Bool(true),
264 );
265 }
266
267 if !nested_col.description.is_empty() {
268 prop_map.insert(
269 serde_yaml::Value::String("description".to_string()),
270 serde_yaml::Value::String(nested_col.description.clone()),
271 );
272 }
273
274 props_array.push(serde_yaml::Value::Mapping(prop_map));
275 }
276 }
277 return Some(props_array);
278 }
279 None
280 }
281
282 fn map_data_type_to_logical_type(data_type: &str) -> (String, bool) {
285 let upper = data_type.to_uppercase();
286
287 if upper.starts_with("ARRAY<") {
289 return ("array".to_string(), true);
290 }
291
292 if upper.contains("INT") || upper == "BIGINT" || upper == "SMALLINT" || upper == "TINYINT" {
294 ("integer".to_string(), false)
295 } else if upper.contains("DECIMAL")
296 || upper.contains("DOUBLE")
297 || upper.contains("FLOAT")
298 || upper.contains("NUMERIC")
299 || upper == "NUMBER"
300 {
301 ("number".to_string(), false)
302 } else if upper == "BOOLEAN" || upper == "BOOL" {
303 ("boolean".to_string(), false)
304 } else if upper == "DATE" {
305 ("date".to_string(), false)
306 } else if upper.contains("TIMESTAMP") {
307 ("timestamp".to_string(), false)
308 } else if upper == "TIME" {
309 ("time".to_string(), false)
310 } else if upper == "STRUCT" || upper == "OBJECT" || upper.starts_with("STRUCT<") {
311 ("object".to_string(), false)
312 } else {
313 ("string".to_string(), false)
315 }
316 }
317
318 fn json_to_yaml_value(json: &serde_json::Value) -> serde_yaml::Value {
320 match json {
321 serde_json::Value::Null => serde_yaml::Value::Null,
322 serde_json::Value::Bool(b) => serde_yaml::Value::Bool(*b),
323 serde_json::Value::Number(n) => {
324 if let Some(i) = n.as_i64() {
325 serde_yaml::Value::Number(serde_yaml::Number::from(i))
326 } else if let Some(f) = n.as_f64() {
327 serde_yaml::Value::Number(serde_yaml::Number::from(f))
328 } else {
329 serde_yaml::Value::String(n.to_string())
330 }
331 }
332 serde_json::Value::String(s) => serde_yaml::Value::String(s.clone()),
333 serde_json::Value::Array(arr) => {
334 let yaml_arr: Vec<serde_yaml::Value> =
335 arr.iter().map(Self::json_to_yaml_value).collect();
336 serde_yaml::Value::Sequence(yaml_arr)
337 }
338 serde_json::Value::Object(obj) => {
339 let mut yaml_map = serde_yaml::Mapping::new();
340 for (k, v) in obj {
341 yaml_map.insert(
342 serde_yaml::Value::String(k.clone()),
343 Self::json_to_yaml_value(v),
344 );
345 }
346 serde_yaml::Value::Mapping(yaml_map)
347 }
348 }
349 }
350
351 fn export_odcs_v3_1_0_format(table: &Table) -> String {
353 let mut yaml = serde_yaml::Mapping::new();
354
355 yaml.insert(
357 serde_yaml::Value::String("apiVersion".to_string()),
358 serde_yaml::Value::String("v3.1.0".to_string()),
359 );
360 yaml.insert(
361 serde_yaml::Value::String("kind".to_string()),
362 serde_yaml::Value::String("DataContract".to_string()),
363 );
364
365 yaml.insert(
367 serde_yaml::Value::String("id".to_string()),
368 serde_yaml::Value::String(table.id.to_string()),
369 );
370
371 yaml.insert(
373 serde_yaml::Value::String("name".to_string()),
374 serde_yaml::Value::String(table.name.clone()),
375 );
376
377 let version = table
379 .odcl_metadata
380 .get("version")
381 .and_then(|v| v.as_str())
382 .map(|s| s.to_string())
383 .unwrap_or_else(|| "1.0.0".to_string());
384 yaml.insert(
385 serde_yaml::Value::String("version".to_string()),
386 serde_yaml::Value::String(version),
387 );
388
389 let status_value = table
391 .odcl_metadata
392 .get("status")
393 .and_then(|v| {
394 if v.is_null() {
395 None
396 } else {
397 Some(Self::json_to_yaml_value(v))
398 }
399 })
400 .unwrap_or_else(|| serde_yaml::Value::String("draft".to_string()));
401 yaml.insert(
402 serde_yaml::Value::String("status".to_string()),
403 status_value,
404 );
405
406 if let Some(domain) = table.odcl_metadata.get("domain")
408 && !domain.is_null()
409 {
410 yaml.insert(
411 serde_yaml::Value::String("domain".to_string()),
412 Self::json_to_yaml_value(domain),
413 );
414 }
415
416 if let Some(data_product) = table.odcl_metadata.get("dataProduct")
418 && !data_product.is_null()
419 {
420 yaml.insert(
421 serde_yaml::Value::String("dataProduct".to_string()),
422 Self::json_to_yaml_value(data_product),
423 );
424 }
425
426 if let Some(tenant) = table.odcl_metadata.get("tenant")
428 && !tenant.is_null()
429 {
430 yaml.insert(
431 serde_yaml::Value::String("tenant".to_string()),
432 Self::json_to_yaml_value(tenant),
433 );
434 }
435
436 if let Some(description) = table.odcl_metadata.get("description")
438 && !description.is_null()
439 {
440 yaml.insert(
441 serde_yaml::Value::String("description".to_string()),
442 Self::json_to_yaml_value(description),
443 );
444 }
445
446 if !table.tags.is_empty() {
448 let tags_yaml: Vec<serde_yaml::Value> = table
449 .tags
450 .iter()
451 .map(|t| serde_yaml::Value::String(t.to_string()))
452 .collect();
453 yaml.insert(
454 serde_yaml::Value::String("tags".to_string()),
455 serde_yaml::Value::Sequence(tags_yaml),
456 );
457 }
458
459 if let Some(team) = table.odcl_metadata.get("team")
461 && !team.is_null()
462 {
463 yaml.insert(
464 serde_yaml::Value::String("team".to_string()),
465 Self::json_to_yaml_value(team),
466 );
467 }
468
469 if let Some(roles) = table.odcl_metadata.get("roles")
471 && !roles.is_null()
472 {
473 yaml.insert(
474 serde_yaml::Value::String("roles".to_string()),
475 Self::json_to_yaml_value(roles),
476 );
477 }
478
479 if let Some(pricing) = table.odcl_metadata.get("pricing")
481 && !pricing.is_null()
482 {
483 yaml.insert(
484 serde_yaml::Value::String("price".to_string()),
485 Self::json_to_yaml_value(pricing),
486 );
487 }
488
489 if let Some(terms) = table.odcl_metadata.get("terms")
491 && !terms.is_null()
492 {
493 yaml.insert(
494 serde_yaml::Value::String("terms".to_string()),
495 Self::json_to_yaml_value(terms),
496 );
497 }
498
499 if let Some(servers) = table.odcl_metadata.get("servers")
501 && !servers.is_null()
502 {
503 yaml.insert(
504 serde_yaml::Value::String("servers".to_string()),
505 Self::json_to_yaml_value(servers),
506 );
507 }
508
509 if let Some(servicelevels) = table.odcl_metadata.get("servicelevels")
511 && !servicelevels.is_null()
512 {
513 yaml.insert(
514 serde_yaml::Value::String("servicelevels".to_string()),
515 Self::json_to_yaml_value(servicelevels),
516 );
517 }
518
519 if let Some(links) = table.odcl_metadata.get("links")
521 && !links.is_null()
522 {
523 yaml.insert(
524 serde_yaml::Value::String("links".to_string()),
525 Self::json_to_yaml_value(links),
526 );
527 }
528
529 if let Some(infrastructure) = table.odcl_metadata.get("infrastructure")
531 && !infrastructure.is_null()
532 {
533 yaml.insert(
534 serde_yaml::Value::String("infrastructure".to_string()),
535 Self::json_to_yaml_value(infrastructure),
536 );
537 }
538
539 let mut schema_array = Vec::new();
541 let mut schema_obj = serde_yaml::Mapping::new();
542
543 schema_obj.insert(
544 serde_yaml::Value::String("name".to_string()),
545 serde_yaml::Value::String(table.name.clone()),
546 );
547
548 let mut properties = Vec::new();
550
551 fn mapping_to_properties_array(props_map: serde_yaml::Mapping) -> Vec<serde_yaml::Value> {
553 let mut props_array = Vec::new();
554 for (key, value) in props_map {
555 if let serde_yaml::Value::String(name) = key
556 && let serde_yaml::Value::Mapping(mut prop_map) = value
557 {
558 prop_map.insert(
560 serde_yaml::Value::String("name".to_string()),
561 serde_yaml::Value::String(name.clone()),
562 );
563 props_array.push(serde_yaml::Value::Mapping(prop_map));
564 }
565 }
566 props_array
567 }
568
569 fn build_nested_properties(
571 parent_name: &str,
572 _table_name: &str,
573 all_columns: &[crate::models::Column],
574 json_to_yaml_fn: &dyn Fn(&serde_json::Value) -> serde_yaml::Value,
575 map_data_type_fn: &dyn Fn(&str) -> (String, bool),
576 ) -> Option<Vec<serde_yaml::Value>> {
577 let parent_prefix_dot = format!("{}.", parent_name);
579 let parent_prefix_array = format!("{}.[].", parent_name); let parent_prefix_array_no_dot = format!("{}.[]", parent_name); let nested_columns: Vec<&crate::models::Column> = all_columns
582 .iter()
583 .filter(|col| {
584 col.name.starts_with(&parent_prefix_dot)
585 || col.name.starts_with(&parent_prefix_array_no_dot)
586 })
587 .collect();
588
589 if nested_columns.is_empty() {
590 return None;
591 }
592
593 let mut nested_props_map = serde_yaml::Mapping::new();
594
595 let mut child_map: std::collections::HashMap<String, Vec<&crate::models::Column>> =
597 std::collections::HashMap::new();
598
599 for nested_col in &nested_columns {
600 let relative_name = if nested_col.name.starts_with(&parent_prefix_array) {
603 nested_col
606 .name
607 .strip_prefix(&parent_prefix_array)
608 .unwrap_or("")
609 } else if nested_col.name.starts_with(&parent_prefix_dot) {
610 nested_col
613 .name
614 .strip_prefix(&parent_prefix_dot)
615 .unwrap_or("")
616 } else {
617 continue;
618 };
619
620 if relative_name.is_empty() {
621 continue;
622 }
623
624 let child_name = if let Some(dot_pos) = relative_name.find('.') {
629 if relative_name.starts_with(".[]") {
631 continue;
634 } else {
635 &relative_name[..dot_pos]
637 }
638 } else {
639 relative_name
641 };
642
643 child_map
644 .entry(child_name.to_string())
645 .or_default()
646 .push(nested_col);
647 }
648
649 for (child_name, child_cols) in child_map {
651 if child_name.is_empty() {
653 continue;
654 }
655 let direct_child = child_cols.iter().find(|col| {
659 let rel_name = if col.name.starts_with(&parent_prefix_array) {
660 col.name.strip_prefix(&parent_prefix_array)
661 } else if col.name.starts_with(&parent_prefix_dot) {
662 col.name.strip_prefix(&parent_prefix_dot)
663 } else {
664 None
665 };
666 rel_name
668 .map(|rel| {
669 rel == child_name
670 || rel.starts_with(&format!("{}.", child_name))
671 || rel.starts_with(&format!("{}.[]", child_name))
672 })
673 .unwrap_or(false)
674 });
675
676 let child_has_nested = child_cols.iter().any(|col| {
678 let rel_name = if col.name.starts_with(&parent_prefix_array) {
679 col.name.strip_prefix(&parent_prefix_array)
680 } else if col.name.starts_with(&parent_prefix_dot) {
681 col.name.strip_prefix(&parent_prefix_dot)
682 } else {
683 None
684 };
685 rel_name
686 .map(|rel| {
687 rel.starts_with(&format!("{}.", child_name)) && rel != child_name
688 })
689 .unwrap_or(false)
690 });
691
692 let child_col = if let Some(col) = direct_child {
697 Some(*col)
698 } else {
699 child_cols
701 .iter()
702 .find(|col| {
703 let rel_name = if col.name.starts_with(&parent_prefix_array) {
704 col.name.strip_prefix(&parent_prefix_array)
705 } else if col.name.starts_with(&parent_prefix_dot) {
706 col.name.strip_prefix(&parent_prefix_dot)
707 } else {
708 None
709 };
710 rel_name.map(|rel| rel == child_name).unwrap_or(false)
711 })
712 .copied()
713 };
714
715 if let Some(child_col) = child_col {
716 let mut child_prop = serde_yaml::Mapping::new();
717
718 child_prop.insert(
720 serde_yaml::Value::String("name".to_string()),
721 serde_yaml::Value::String(child_name.clone()),
722 );
723
724 let data_type_upper = child_col.data_type.to_uppercase();
726 let is_array_object = data_type_upper.starts_with("ARRAY<")
727 && (data_type_upper.contains("OBJECT")
728 || data_type_upper.contains("STRUCT"));
729 let is_struct_or_object = data_type_upper == "STRUCT"
730 || data_type_upper == "OBJECT"
731 || data_type_upper.starts_with("STRUCT<");
732
733 let nested_props_array = build_nested_properties(
735 &child_col.name,
736 _table_name,
737 all_columns,
738 json_to_yaml_fn,
739 map_data_type_fn,
740 );
741
742 if is_array_object && (child_has_nested || nested_props_array.is_some()) {
743 child_prop.insert(
745 serde_yaml::Value::String("logicalType".to_string()),
746 serde_yaml::Value::String("array".to_string()),
747 );
748 child_prop.insert(
749 serde_yaml::Value::String("physicalType".to_string()),
750 serde_yaml::Value::String(get_physical_type(child_col)),
751 );
752
753 let mut items = serde_yaml::Mapping::new();
754 items.insert(
755 serde_yaml::Value::String("logicalType".to_string()),
756 serde_yaml::Value::String("object".to_string()),
757 );
758
759 if let Some(nested_props) = nested_props_array {
761 items.insert(
762 serde_yaml::Value::String("properties".to_string()),
763 serde_yaml::Value::Sequence(nested_props),
764 );
765 }
766
767 child_prop.insert(
768 serde_yaml::Value::String("items".to_string()),
769 serde_yaml::Value::Mapping(items),
770 );
771 } else if is_struct_or_object
772 || child_has_nested
773 || nested_props_array.is_some()
774 {
775 child_prop.insert(
777 serde_yaml::Value::String("logicalType".to_string()),
778 serde_yaml::Value::String("object".to_string()),
779 );
780 child_prop.insert(
781 serde_yaml::Value::String("physicalType".to_string()),
782 serde_yaml::Value::String(get_physical_type(child_col)),
783 );
784
785 if let Some(nested_props) = nested_props_array {
787 child_prop.insert(
788 serde_yaml::Value::String("properties".to_string()),
789 serde_yaml::Value::Sequence(nested_props),
790 );
791 }
792 } else {
793 let (logical_type, _) = map_data_type_fn(&child_col.data_type);
795 child_prop.insert(
796 serde_yaml::Value::String("logicalType".to_string()),
797 serde_yaml::Value::String(logical_type),
798 );
799 child_prop.insert(
800 serde_yaml::Value::String("physicalType".to_string()),
801 serde_yaml::Value::String(get_physical_type(child_col)),
802 );
803 }
804
805 if !child_col.nullable {
806 child_prop.insert(
807 serde_yaml::Value::String("required".to_string()),
808 serde_yaml::Value::Bool(true),
809 );
810 }
811
812 if !child_col.description.is_empty() {
813 child_prop.insert(
814 serde_yaml::Value::String("description".to_string()),
815 serde_yaml::Value::String(child_col.description.clone()),
816 );
817 }
818
819 if !child_col.quality.is_empty() {
821 let quality_array: Vec<serde_yaml::Value> = child_col
822 .quality
823 .iter()
824 .map(|rule| {
825 let mut rule_map = serde_yaml::Mapping::new();
826 for (k, v) in rule {
827 rule_map.insert(
828 serde_yaml::Value::String(k.clone()),
829 json_to_yaml_fn(v),
830 );
831 }
832 serde_yaml::Value::Mapping(rule_map)
833 })
834 .collect();
835 child_prop.insert(
836 serde_yaml::Value::String("quality".to_string()),
837 serde_yaml::Value::Sequence(quality_array),
838 );
839 }
840
841 if !child_col.relationships.is_empty() {
843 let rels_yaml: Vec<serde_yaml::Value> = child_col
844 .relationships
845 .iter()
846 .map(|rel| {
847 let mut rel_map = serde_yaml::Mapping::new();
848 rel_map.insert(
849 serde_yaml::Value::String("type".to_string()),
850 serde_yaml::Value::String(rel.relationship_type.clone()),
851 );
852 rel_map.insert(
853 serde_yaml::Value::String("to".to_string()),
854 serde_yaml::Value::String(rel.to.clone()),
855 );
856 serde_yaml::Value::Mapping(rel_map)
857 })
858 .collect();
859 child_prop.insert(
860 serde_yaml::Value::String("relationships".to_string()),
861 serde_yaml::Value::Sequence(rels_yaml),
862 );
863 }
864
865 if !child_col.enum_values.is_empty() {
868 let quality = child_prop
869 .entry(serde_yaml::Value::String("quality".to_string()))
870 .or_insert_with(|| serde_yaml::Value::Sequence(Vec::new()));
871
872 if let serde_yaml::Value::Sequence(quality_rules) = quality {
873 let mut enum_rule = serde_yaml::Mapping::new();
874 enum_rule.insert(
875 serde_yaml::Value::String("type".to_string()),
876 serde_yaml::Value::String("sql".to_string()),
877 );
878
879 let enum_list: String = child_col
880 .enum_values
881 .iter()
882 .map(|e| format!("'{}'", e.replace('\'', "''")))
883 .collect::<Vec<_>>()
884 .join(", ");
885 let query = format!(
886 "SELECT COUNT(*) FROM ${{table}} WHERE ${{column}} NOT IN ({})",
887 enum_list
888 );
889
890 enum_rule.insert(
891 serde_yaml::Value::String("query".to_string()),
892 serde_yaml::Value::String(query),
893 );
894
895 enum_rule.insert(
896 serde_yaml::Value::String("mustBe".to_string()),
897 serde_yaml::Value::Number(serde_yaml::Number::from(0)),
898 );
899
900 enum_rule.insert(
901 serde_yaml::Value::String("description".to_string()),
902 serde_yaml::Value::String(format!(
903 "Value must be one of: {}",
904 child_col.enum_values.join(", ")
905 )),
906 );
907
908 quality_rules.push(serde_yaml::Value::Mapping(enum_rule));
909 }
910 }
911
912 if !child_col.constraints.is_empty() {
914 let constraints_yaml: Vec<serde_yaml::Value> = child_col
915 .constraints
916 .iter()
917 .map(|c| serde_yaml::Value::String(c.clone()))
918 .collect();
919 child_prop.insert(
920 serde_yaml::Value::String("constraints".to_string()),
921 serde_yaml::Value::Sequence(constraints_yaml),
922 );
923 }
924
925 if let Some(ref fk) = child_col.foreign_key {
927 let mut fk_map = serde_yaml::Mapping::new();
928 fk_map.insert(
929 serde_yaml::Value::String("table".to_string()),
930 serde_yaml::Value::String(fk.table_id.clone()),
931 );
932 fk_map.insert(
933 serde_yaml::Value::String("column".to_string()),
934 serde_yaml::Value::String(fk.column_name.clone()),
935 );
936 child_prop.insert(
937 serde_yaml::Value::String("foreignKey".to_string()),
938 serde_yaml::Value::Mapping(fk_map),
939 );
940 }
941
942 if let Some(ref biz_name) = child_col.business_name {
946 child_prop.insert(
947 serde_yaml::Value::String("businessName".to_string()),
948 serde_yaml::Value::String(biz_name.clone()),
949 );
950 }
951
952 if let Some(ref phys_name) = child_col.physical_name {
954 child_prop.insert(
955 serde_yaml::Value::String("physicalName".to_string()),
956 serde_yaml::Value::String(phys_name.clone()),
957 );
958 }
959
960 if child_col.unique {
962 child_prop.insert(
963 serde_yaml::Value::String("unique".to_string()),
964 serde_yaml::Value::Bool(true),
965 );
966 }
967
968 if child_col.primary_key {
970 child_prop.insert(
971 serde_yaml::Value::String("primaryKey".to_string()),
972 serde_yaml::Value::Bool(true),
973 );
974 }
975
976 if let Some(pk_pos) = child_col.primary_key_position {
978 child_prop.insert(
979 serde_yaml::Value::String("primaryKeyPosition".to_string()),
980 serde_yaml::Value::Number(serde_yaml::Number::from(pk_pos)),
981 );
982 }
983
984 if child_col.partitioned {
986 child_prop.insert(
987 serde_yaml::Value::String("partitioned".to_string()),
988 serde_yaml::Value::Bool(true),
989 );
990 }
991
992 if let Some(pos) = child_col.partition_key_position {
994 child_prop.insert(
995 serde_yaml::Value::String("partitionKeyPosition".to_string()),
996 serde_yaml::Value::Number(serde_yaml::Number::from(pos)),
997 );
998 }
999
1000 if child_col.clustered {
1002 child_prop.insert(
1003 serde_yaml::Value::String("clustered".to_string()),
1004 serde_yaml::Value::Bool(true),
1005 );
1006 }
1007
1008 if let Some(ref class) = child_col.classification {
1010 child_prop.insert(
1011 serde_yaml::Value::String("classification".to_string()),
1012 serde_yaml::Value::String(class.clone()),
1013 );
1014 }
1015
1016 if child_col.critical_data_element {
1018 child_prop.insert(
1019 serde_yaml::Value::String("criticalDataElement".to_string()),
1020 serde_yaml::Value::Bool(true),
1021 );
1022 }
1023
1024 if let Some(ref enc) = child_col.encrypted_name {
1026 child_prop.insert(
1027 serde_yaml::Value::String("encryptedName".to_string()),
1028 serde_yaml::Value::String(enc.clone()),
1029 );
1030 }
1031
1032 if !child_col.transform_source_objects.is_empty() {
1034 let sources: Vec<serde_yaml::Value> = child_col
1035 .transform_source_objects
1036 .iter()
1037 .map(|s| serde_yaml::Value::String(s.clone()))
1038 .collect();
1039 child_prop.insert(
1040 serde_yaml::Value::String("transformSourceObjects".to_string()),
1041 serde_yaml::Value::Sequence(sources),
1042 );
1043 }
1044
1045 if let Some(ref logic) = child_col.transform_logic {
1047 child_prop.insert(
1048 serde_yaml::Value::String("transformLogic".to_string()),
1049 serde_yaml::Value::String(logic.clone()),
1050 );
1051 }
1052
1053 if let Some(ref desc) = child_col.transform_description {
1055 child_prop.insert(
1056 serde_yaml::Value::String("transformDescription".to_string()),
1057 serde_yaml::Value::String(desc.clone()),
1058 );
1059 }
1060
1061 if !child_col.examples.is_empty() {
1063 let examples: Vec<serde_yaml::Value> =
1064 child_col.examples.iter().map(json_to_yaml_fn).collect();
1065 child_prop.insert(
1066 serde_yaml::Value::String("examples".to_string()),
1067 serde_yaml::Value::Sequence(examples),
1068 );
1069 }
1070
1071 if !child_col.authoritative_definitions.is_empty() {
1073 let defs: Vec<serde_yaml::Value> = child_col
1074 .authoritative_definitions
1075 .iter()
1076 .map(|d| {
1077 let mut def_map = serde_yaml::Mapping::new();
1078 def_map.insert(
1079 serde_yaml::Value::String("type".to_string()),
1080 serde_yaml::Value::String(d.definition_type.clone()),
1081 );
1082 def_map.insert(
1083 serde_yaml::Value::String("url".to_string()),
1084 serde_yaml::Value::String(d.url.clone()),
1085 );
1086 serde_yaml::Value::Mapping(def_map)
1087 })
1088 .collect();
1089 child_prop.insert(
1090 serde_yaml::Value::String("authoritativeDefinitions".to_string()),
1091 serde_yaml::Value::Sequence(defs),
1092 );
1093 }
1094
1095 if !child_col.tags.is_empty() {
1097 let tags: Vec<serde_yaml::Value> = child_col
1098 .tags
1099 .iter()
1100 .map(|t| serde_yaml::Value::String(t.clone()))
1101 .collect();
1102 child_prop.insert(
1103 serde_yaml::Value::String("tags".to_string()),
1104 serde_yaml::Value::Sequence(tags),
1105 );
1106 }
1107
1108 if !child_col.custom_properties.is_empty() {
1110 let props: Vec<serde_yaml::Value> = child_col
1111 .custom_properties
1112 .iter()
1113 .map(|(k, v)| {
1114 let mut m = serde_yaml::Mapping::new();
1115 m.insert(
1116 serde_yaml::Value::String("property".to_string()),
1117 serde_yaml::Value::String(k.clone()),
1118 );
1119 m.insert(
1120 serde_yaml::Value::String("value".to_string()),
1121 json_to_yaml_fn(v),
1122 );
1123 serde_yaml::Value::Mapping(m)
1124 })
1125 .collect();
1126 child_prop.insert(
1127 serde_yaml::Value::String("customProperties".to_string()),
1128 serde_yaml::Value::Sequence(props),
1129 );
1130 }
1131
1132 if child_col.secondary_key {
1134 child_prop.insert(
1135 serde_yaml::Value::String("businessKey".to_string()),
1136 serde_yaml::Value::Bool(true),
1137 );
1138 }
1139
1140 nested_props_map.insert(
1141 serde_yaml::Value::String(child_name.clone()),
1142 serde_yaml::Value::Mapping(child_prop),
1143 );
1144 } else if !child_cols.is_empty() {
1145 if let Some(first_col) = child_cols.iter().find(|col| {
1148 let rel_name = if col.name.starts_with(&parent_prefix_array) {
1149 col.name.strip_prefix(&parent_prefix_array)
1150 } else if col.name.starts_with(&parent_prefix_dot) {
1151 col.name.strip_prefix(&parent_prefix_dot)
1152 } else {
1153 None
1154 };
1155 rel_name.map(|rel| rel == child_name).unwrap_or(false)
1156 }) {
1157 let mut child_prop = serde_yaml::Mapping::new();
1158
1159 child_prop.insert(
1161 serde_yaml::Value::String("name".to_string()),
1162 serde_yaml::Value::String(child_name.clone()),
1163 );
1164
1165 let child_has_nested = child_cols.iter().any(|col| {
1167 let rel_name = if col.name.starts_with(&parent_prefix_array) {
1168 col.name.strip_prefix(&parent_prefix_array)
1169 } else if col.name.starts_with(&parent_prefix_dot) {
1170 col.name.strip_prefix(&parent_prefix_dot)
1171 } else {
1172 None
1173 };
1174 rel_name
1175 .map(|rel| {
1176 rel.starts_with(&format!("{}.", child_name))
1177 && rel != child_name
1178 })
1179 .unwrap_or(false)
1180 });
1181
1182 let nested_props_array = if child_has_nested {
1184 build_nested_properties(
1185 &first_col.name,
1186 _table_name,
1187 all_columns,
1188 json_to_yaml_fn,
1189 map_data_type_fn,
1190 )
1191 } else {
1192 None
1193 };
1194
1195 let data_type_upper = first_col.data_type.to_uppercase();
1196 let is_array_object = data_type_upper.starts_with("ARRAY<")
1197 && (data_type_upper.contains("OBJECT")
1198 || data_type_upper.contains("STRUCT"));
1199 let is_struct_or_object = data_type_upper == "STRUCT"
1200 || data_type_upper == "OBJECT"
1201 || data_type_upper.starts_with("STRUCT<");
1202
1203 if is_array_object && (child_has_nested || nested_props_array.is_some()) {
1204 child_prop.insert(
1205 serde_yaml::Value::String("logicalType".to_string()),
1206 serde_yaml::Value::String("array".to_string()),
1207 );
1208 child_prop.insert(
1209 serde_yaml::Value::String("physicalType".to_string()),
1210 serde_yaml::Value::String(get_physical_type(first_col)),
1211 );
1212
1213 let mut items = serde_yaml::Mapping::new();
1214 items.insert(
1215 serde_yaml::Value::String("logicalType".to_string()),
1216 serde_yaml::Value::String("object".to_string()),
1217 );
1218
1219 if let Some(nested_props) = nested_props_array {
1220 items.insert(
1221 serde_yaml::Value::String("properties".to_string()),
1222 serde_yaml::Value::Sequence(nested_props),
1223 );
1224 }
1225
1226 child_prop.insert(
1227 serde_yaml::Value::String("items".to_string()),
1228 serde_yaml::Value::Mapping(items),
1229 );
1230 } else if is_struct_or_object
1231 || child_has_nested
1232 || nested_props_array.is_some()
1233 {
1234 child_prop.insert(
1235 serde_yaml::Value::String("logicalType".to_string()),
1236 serde_yaml::Value::String("object".to_string()),
1237 );
1238 child_prop.insert(
1239 serde_yaml::Value::String("physicalType".to_string()),
1240 serde_yaml::Value::String(get_physical_type(first_col)),
1241 );
1242
1243 if let Some(nested_props) = nested_props_array {
1244 child_prop.insert(
1245 serde_yaml::Value::String("properties".to_string()),
1246 serde_yaml::Value::Sequence(nested_props),
1247 );
1248 }
1249 } else {
1250 let (logical_type, _) = map_data_type_fn(&first_col.data_type);
1251 child_prop.insert(
1252 serde_yaml::Value::String("logicalType".to_string()),
1253 serde_yaml::Value::String(logical_type),
1254 );
1255 child_prop.insert(
1256 serde_yaml::Value::String("physicalType".to_string()),
1257 serde_yaml::Value::String(get_physical_type(first_col)),
1258 );
1259 }
1260
1261 if !first_col.nullable {
1262 child_prop.insert(
1263 serde_yaml::Value::String("required".to_string()),
1264 serde_yaml::Value::Bool(true),
1265 );
1266 }
1267
1268 if !first_col.description.is_empty() {
1269 child_prop.insert(
1270 serde_yaml::Value::String("description".to_string()),
1271 serde_yaml::Value::String(first_col.description.clone()),
1272 );
1273 }
1274
1275 if let Some(ref biz_name) = first_col.business_name {
1279 child_prop.insert(
1280 serde_yaml::Value::String("businessName".to_string()),
1281 serde_yaml::Value::String(biz_name.clone()),
1282 );
1283 }
1284
1285 if let Some(ref phys_name) = first_col.physical_name {
1287 child_prop.insert(
1288 serde_yaml::Value::String("physicalName".to_string()),
1289 serde_yaml::Value::String(phys_name.clone()),
1290 );
1291 }
1292
1293 if first_col.unique {
1295 child_prop.insert(
1296 serde_yaml::Value::String("unique".to_string()),
1297 serde_yaml::Value::Bool(true),
1298 );
1299 }
1300
1301 if first_col.primary_key {
1303 child_prop.insert(
1304 serde_yaml::Value::String("primaryKey".to_string()),
1305 serde_yaml::Value::Bool(true),
1306 );
1307 }
1308
1309 if let Some(pk_pos) = first_col.primary_key_position {
1311 child_prop.insert(
1312 serde_yaml::Value::String("primaryKeyPosition".to_string()),
1313 serde_yaml::Value::Number(serde_yaml::Number::from(pk_pos)),
1314 );
1315 }
1316
1317 if first_col.partitioned {
1319 child_prop.insert(
1320 serde_yaml::Value::String("partitioned".to_string()),
1321 serde_yaml::Value::Bool(true),
1322 );
1323 }
1324
1325 if let Some(pos) = first_col.partition_key_position {
1327 child_prop.insert(
1328 serde_yaml::Value::String("partitionKeyPosition".to_string()),
1329 serde_yaml::Value::Number(serde_yaml::Number::from(pos)),
1330 );
1331 }
1332
1333 if first_col.clustered {
1335 child_prop.insert(
1336 serde_yaml::Value::String("clustered".to_string()),
1337 serde_yaml::Value::Bool(true),
1338 );
1339 }
1340
1341 if let Some(ref class) = first_col.classification {
1343 child_prop.insert(
1344 serde_yaml::Value::String("classification".to_string()),
1345 serde_yaml::Value::String(class.clone()),
1346 );
1347 }
1348
1349 if first_col.critical_data_element {
1351 child_prop.insert(
1352 serde_yaml::Value::String("criticalDataElement".to_string()),
1353 serde_yaml::Value::Bool(true),
1354 );
1355 }
1356
1357 if let Some(ref enc) = first_col.encrypted_name {
1359 child_prop.insert(
1360 serde_yaml::Value::String("encryptedName".to_string()),
1361 serde_yaml::Value::String(enc.clone()),
1362 );
1363 }
1364
1365 if !first_col.transform_source_objects.is_empty() {
1367 let sources: Vec<serde_yaml::Value> = first_col
1368 .transform_source_objects
1369 .iter()
1370 .map(|s| serde_yaml::Value::String(s.clone()))
1371 .collect();
1372 child_prop.insert(
1373 serde_yaml::Value::String("transformSourceObjects".to_string()),
1374 serde_yaml::Value::Sequence(sources),
1375 );
1376 }
1377
1378 if let Some(ref logic) = first_col.transform_logic {
1380 child_prop.insert(
1381 serde_yaml::Value::String("transformLogic".to_string()),
1382 serde_yaml::Value::String(logic.clone()),
1383 );
1384 }
1385
1386 if let Some(ref desc) = first_col.transform_description {
1388 child_prop.insert(
1389 serde_yaml::Value::String("transformDescription".to_string()),
1390 serde_yaml::Value::String(desc.clone()),
1391 );
1392 }
1393
1394 if !first_col.examples.is_empty() {
1396 let examples: Vec<serde_yaml::Value> =
1397 first_col.examples.iter().map(json_to_yaml_fn).collect();
1398 child_prop.insert(
1399 serde_yaml::Value::String("examples".to_string()),
1400 serde_yaml::Value::Sequence(examples),
1401 );
1402 }
1403
1404 if !first_col.authoritative_definitions.is_empty() {
1406 let defs: Vec<serde_yaml::Value> = first_col
1407 .authoritative_definitions
1408 .iter()
1409 .map(|d| {
1410 let mut def_map = serde_yaml::Mapping::new();
1411 def_map.insert(
1412 serde_yaml::Value::String("type".to_string()),
1413 serde_yaml::Value::String(d.definition_type.clone()),
1414 );
1415 def_map.insert(
1416 serde_yaml::Value::String("url".to_string()),
1417 serde_yaml::Value::String(d.url.clone()),
1418 );
1419 serde_yaml::Value::Mapping(def_map)
1420 })
1421 .collect();
1422 child_prop.insert(
1423 serde_yaml::Value::String("authoritativeDefinitions".to_string()),
1424 serde_yaml::Value::Sequence(defs),
1425 );
1426 }
1427
1428 if !first_col.tags.is_empty() {
1430 let tags: Vec<serde_yaml::Value> = first_col
1431 .tags
1432 .iter()
1433 .map(|t| serde_yaml::Value::String(t.clone()))
1434 .collect();
1435 child_prop.insert(
1436 serde_yaml::Value::String("tags".to_string()),
1437 serde_yaml::Value::Sequence(tags),
1438 );
1439 }
1440
1441 if !first_col.custom_properties.is_empty() {
1443 let props: Vec<serde_yaml::Value> = first_col
1444 .custom_properties
1445 .iter()
1446 .map(|(k, v)| {
1447 let mut m = serde_yaml::Mapping::new();
1448 m.insert(
1449 serde_yaml::Value::String("property".to_string()),
1450 serde_yaml::Value::String(k.clone()),
1451 );
1452 m.insert(
1453 serde_yaml::Value::String("value".to_string()),
1454 json_to_yaml_fn(v),
1455 );
1456 serde_yaml::Value::Mapping(m)
1457 })
1458 .collect();
1459 child_prop.insert(
1460 serde_yaml::Value::String("customProperties".to_string()),
1461 serde_yaml::Value::Sequence(props),
1462 );
1463 }
1464
1465 if first_col.secondary_key {
1467 child_prop.insert(
1468 serde_yaml::Value::String("businessKey".to_string()),
1469 serde_yaml::Value::Bool(true),
1470 );
1471 }
1472
1473 nested_props_map.insert(
1474 serde_yaml::Value::String(child_name.clone()),
1475 serde_yaml::Value::Mapping(child_prop),
1476 );
1477 }
1478 } else {
1479 continue;
1481 }
1482 }
1483
1484 if nested_props_map.is_empty() {
1485 None
1486 } else {
1487 Some(mapping_to_properties_array(nested_props_map))
1489 }
1490 }
1491
1492 for column in &table.columns {
1493 if column.name.contains('.') {
1495 continue;
1496 }
1497
1498 let mut prop = serde_yaml::Mapping::new();
1499
1500 let column_prefix_dot = format!("{}.", column.name);
1504 let column_prefix_array = format!("{}.[]", column.name);
1505 let has_nested = table.columns.iter().any(|col| {
1506 (col.name.starts_with(&column_prefix_dot)
1507 || col.name.starts_with(&column_prefix_array))
1508 && col.name != column.name
1509 });
1510
1511 let data_type_upper = column.data_type.to_uppercase();
1513
1514 let is_array_struct_from_desc =
1518 data_type_upper == "ARRAY" && column.description.contains("|| ARRAY<STRUCT<");
1519 let is_array_struct_from_nested = (data_type_upper == "ARRAY"
1520 || data_type_upper == "ARRAY<OBJECT>")
1521 && has_nested
1522 && table
1523 .columns
1524 .iter()
1525 .any(|col| col.name.starts_with(&format!("{}.[]", column.name)));
1526
1527 let is_array_struct_from_string_type = data_type_upper == "ARRAY<STRING>"
1529 && column.description.contains("|| ARRAY<STRUCT<");
1530
1531 let is_array_struct = is_array_struct_from_desc
1532 || is_array_struct_from_nested
1533 || is_array_struct_from_string_type;
1534
1535 let full_array_struct_type =
1537 if is_array_struct_from_desc || is_array_struct_from_string_type {
1538 column
1539 .description
1540 .split("|| ")
1541 .nth(1)
1542 .map(|s| s.trim().to_string())
1543 } else if is_array_struct_from_nested {
1544 None } else {
1548 None
1549 };
1550
1551 let is_array_object = data_type_upper.starts_with("ARRAY<")
1552 && (data_type_upper.contains("OBJECT") || data_type_upper.contains("STRUCT"));
1553 let is_struct_or_object = data_type_upper == "STRUCT"
1554 || data_type_upper == "OBJECT"
1555 || data_type_upper.starts_with("STRUCT<");
1556
1557 if is_array_struct {
1559 let struct_props = if let Some(ref full_type) = full_array_struct_type {
1560 Self::parse_struct_properties_from_data_type(
1562 &column.name,
1563 full_type,
1564 &Self::map_data_type_to_logical_type,
1565 )
1566 } else if is_array_struct_from_nested {
1567 build_nested_properties(
1569 &column.name,
1570 &table.name,
1571 &table.columns,
1572 &Self::json_to_yaml_value,
1573 &Self::map_data_type_to_logical_type,
1574 )
1575 } else {
1576 None
1577 };
1578
1579 if let Some(props) = struct_props {
1580 prop.insert(
1582 serde_yaml::Value::String("logicalType".to_string()),
1583 serde_yaml::Value::String("array".to_string()),
1584 );
1585 prop.insert(
1586 serde_yaml::Value::String("physicalType".to_string()),
1587 serde_yaml::Value::String("ARRAY".to_string()),
1588 );
1589
1590 let mut items = serde_yaml::Mapping::new();
1591 items.insert(
1592 serde_yaml::Value::String("logicalType".to_string()),
1593 serde_yaml::Value::String("object".to_string()),
1594 );
1595 items.insert(
1596 serde_yaml::Value::String("properties".to_string()),
1597 serde_yaml::Value::Sequence(props),
1598 );
1599
1600 prop.insert(
1601 serde_yaml::Value::String("items".to_string()),
1602 serde_yaml::Value::Mapping(items),
1603 );
1604
1605 if is_array_struct_from_desc {
1607 let base_description =
1608 column.description.split("||").next().unwrap_or("").trim();
1609 if !base_description.is_empty() {
1610 prop.insert(
1611 serde_yaml::Value::String("description".to_string()),
1612 serde_yaml::Value::String(base_description.to_string()),
1613 );
1614 }
1615 } else if !column.description.is_empty() {
1616 prop.insert(
1618 serde_yaml::Value::String("description".to_string()),
1619 serde_yaml::Value::String(column.description.clone()),
1620 );
1621 }
1622 } else {
1623 prop.insert(
1625 serde_yaml::Value::String("logicalType".to_string()),
1626 serde_yaml::Value::String("array".to_string()),
1627 );
1628 prop.insert(
1629 serde_yaml::Value::String("physicalType".to_string()),
1630 serde_yaml::Value::String("ARRAY".to_string()),
1631 );
1632 }
1633 }
1634 else if has_nested {
1637 let nested_props = build_nested_properties(
1639 &column.name,
1640 &table.name,
1641 &table.columns,
1642 &Self::json_to_yaml_value,
1643 &Self::map_data_type_to_logical_type,
1644 );
1645
1646 if is_array_object {
1647 prop.insert(
1649 serde_yaml::Value::String("logicalType".to_string()),
1650 serde_yaml::Value::String("array".to_string()),
1651 );
1652 prop.insert(
1653 serde_yaml::Value::String("physicalType".to_string()),
1654 serde_yaml::Value::String(get_physical_type(column)),
1655 );
1656
1657 let mut items = serde_yaml::Mapping::new();
1658 items.insert(
1659 serde_yaml::Value::String("logicalType".to_string()),
1660 serde_yaml::Value::String("object".to_string()),
1661 );
1662
1663 if let Some(nested_props_array) = nested_props {
1665 items.insert(
1666 serde_yaml::Value::String("properties".to_string()),
1667 serde_yaml::Value::Sequence(nested_props_array),
1668 );
1669 }
1670
1671 prop.insert(
1672 serde_yaml::Value::String("items".to_string()),
1673 serde_yaml::Value::Mapping(items),
1674 );
1675 } else if is_struct_or_object || nested_props.is_some() {
1676 prop.insert(
1678 serde_yaml::Value::String("logicalType".to_string()),
1679 serde_yaml::Value::String("object".to_string()),
1680 );
1681 prop.insert(
1682 serde_yaml::Value::String("physicalType".to_string()),
1683 serde_yaml::Value::String(get_physical_type(column)),
1684 );
1685
1686 if let Some(nested_props_array) = nested_props {
1688 prop.insert(
1689 serde_yaml::Value::String("properties".to_string()),
1690 serde_yaml::Value::Sequence(nested_props_array),
1691 );
1692 }
1693 } else {
1694 if is_array_object && has_nested {
1697 prop.insert(
1699 serde_yaml::Value::String("logicalType".to_string()),
1700 serde_yaml::Value::String("array".to_string()),
1701 );
1702 prop.insert(
1703 serde_yaml::Value::String("physicalType".to_string()),
1704 serde_yaml::Value::String("ARRAY".to_string()),
1705 );
1706
1707 let mut items = serde_yaml::Mapping::new();
1708 items.insert(
1709 serde_yaml::Value::String("logicalType".to_string()),
1710 serde_yaml::Value::String("object".to_string()),
1711 );
1712
1713 if let Some(nested_props_array) = build_nested_properties(
1715 &column.name,
1716 &table.name,
1717 &table.columns,
1718 &Self::json_to_yaml_value,
1719 &Self::map_data_type_to_logical_type,
1720 ) {
1721 items.insert(
1722 serde_yaml::Value::String("properties".to_string()),
1723 serde_yaml::Value::Sequence(nested_props_array),
1724 );
1725 }
1726
1727 prop.insert(
1728 serde_yaml::Value::String("items".to_string()),
1729 serde_yaml::Value::Mapping(items),
1730 );
1731 } else {
1732 let (logical_type, _) =
1733 Self::map_data_type_to_logical_type(&column.data_type);
1734 prop.insert(
1735 serde_yaml::Value::String("logicalType".to_string()),
1736 serde_yaml::Value::String(logical_type),
1737 );
1738 prop.insert(
1739 serde_yaml::Value::String("physicalType".to_string()),
1740 serde_yaml::Value::String(get_physical_type(column)),
1741 );
1742 }
1743 }
1744 } else if is_struct_or_object {
1745 let parsed_props = Self::parse_struct_properties_from_data_type(
1748 &column.name,
1749 &column.data_type,
1750 &Self::map_data_type_to_logical_type,
1751 );
1752
1753 prop.insert(
1754 serde_yaml::Value::String("logicalType".to_string()),
1755 serde_yaml::Value::String("object".to_string()),
1756 );
1757 prop.insert(
1758 serde_yaml::Value::String("physicalType".to_string()),
1759 serde_yaml::Value::String(get_physical_type(column)),
1760 );
1761
1762 if let Some(nested_props_array) = parsed_props {
1764 prop.insert(
1765 serde_yaml::Value::String("properties".to_string()),
1766 serde_yaml::Value::Sequence(nested_props_array),
1767 );
1768 }
1769 } else if prop.is_empty() {
1770 let (logical_type, _) = Self::map_data_type_to_logical_type(&column.data_type);
1772 prop.insert(
1773 serde_yaml::Value::String("logicalType".to_string()),
1774 serde_yaml::Value::String(logical_type),
1775 );
1776 prop.insert(
1777 serde_yaml::Value::String("physicalType".to_string()),
1778 serde_yaml::Value::String(get_physical_type(column)),
1779 );
1780 }
1781 if !column.nullable {
1784 prop.insert(
1785 serde_yaml::Value::String("required".to_string()),
1786 serde_yaml::Value::Bool(true),
1787 );
1788 }
1789
1790 if column.primary_key {
1791 prop.insert(
1792 serde_yaml::Value::String("primaryKey".to_string()),
1793 serde_yaml::Value::Bool(true),
1794 );
1795 }
1796
1797 if column.secondary_key {
1798 prop.insert(
1799 serde_yaml::Value::String("businessKey".to_string()),
1800 serde_yaml::Value::Bool(true),
1801 );
1802 }
1803
1804 if !column.description.is_empty() {
1805 prop.insert(
1806 serde_yaml::Value::String("description".to_string()),
1807 serde_yaml::Value::String(column.description.clone()),
1808 );
1809 }
1810
1811 if !column.quality.is_empty() {
1813 let quality_array: Vec<serde_yaml::Value> = column
1814 .quality
1815 .iter()
1816 .map(|rule| {
1817 let mut rule_map = serde_yaml::Mapping::new();
1818 for (k, v) in rule {
1819 rule_map.insert(
1820 serde_yaml::Value::String(k.clone()),
1821 Self::json_to_yaml_value(v),
1822 );
1823 }
1824 serde_yaml::Value::Mapping(rule_map)
1825 })
1826 .collect();
1827 prop.insert(
1828 serde_yaml::Value::String("quality".to_string()),
1829 serde_yaml::Value::Sequence(quality_array),
1830 );
1831 }
1832
1833 if !column.relationships.is_empty() {
1835 let rels_yaml: Vec<serde_yaml::Value> = column
1836 .relationships
1837 .iter()
1838 .map(|rel| {
1839 let mut rel_map = serde_yaml::Mapping::new();
1840 rel_map.insert(
1841 serde_yaml::Value::String("type".to_string()),
1842 serde_yaml::Value::String(rel.relationship_type.clone()),
1843 );
1844 rel_map.insert(
1845 serde_yaml::Value::String("to".to_string()),
1846 serde_yaml::Value::String(rel.to.clone()),
1847 );
1848 serde_yaml::Value::Mapping(rel_map)
1849 })
1850 .collect();
1851 prop.insert(
1852 serde_yaml::Value::String("relationships".to_string()),
1853 serde_yaml::Value::Sequence(rels_yaml),
1854 );
1855 }
1856
1857 if !column.enum_values.is_empty() {
1861 let quality = prop
1863 .entry(serde_yaml::Value::String("quality".to_string()))
1864 .or_insert_with(|| serde_yaml::Value::Sequence(Vec::new()));
1865
1866 if let serde_yaml::Value::Sequence(quality_rules) = quality {
1867 let mut enum_rule = serde_yaml::Mapping::new();
1870 enum_rule.insert(
1871 serde_yaml::Value::String("type".to_string()),
1872 serde_yaml::Value::String("sql".to_string()),
1873 );
1874
1875 let enum_list: String = column
1877 .enum_values
1878 .iter()
1879 .map(|e| format!("'{}'", e.replace('\'', "''"))) .collect::<Vec<_>>()
1881 .join(", ");
1882 let query = format!(
1883 "SELECT COUNT(*) FROM ${{table}} WHERE ${{column}} NOT IN ({})",
1884 enum_list
1885 );
1886
1887 enum_rule.insert(
1888 serde_yaml::Value::String("query".to_string()),
1889 serde_yaml::Value::String(query),
1890 );
1891
1892 enum_rule.insert(
1893 serde_yaml::Value::String("mustBe".to_string()),
1894 serde_yaml::Value::Number(serde_yaml::Number::from(0)),
1895 );
1896
1897 enum_rule.insert(
1898 serde_yaml::Value::String("description".to_string()),
1899 serde_yaml::Value::String(format!(
1900 "Value must be one of: {}",
1901 column.enum_values.join(", ")
1902 )),
1903 );
1904
1905 quality_rules.push(serde_yaml::Value::Mapping(enum_rule));
1906 }
1907 }
1908
1909 if !column.constraints.is_empty() {
1911 let constraints_yaml: Vec<serde_yaml::Value> = column
1912 .constraints
1913 .iter()
1914 .map(|c| serde_yaml::Value::String(c.clone()))
1915 .collect();
1916 prop.insert(
1917 serde_yaml::Value::String("constraints".to_string()),
1918 serde_yaml::Value::Sequence(constraints_yaml),
1919 );
1920 }
1921
1922 if let Some(ref biz_name) = column.business_name {
1926 prop.insert(
1927 serde_yaml::Value::String("businessName".to_string()),
1928 serde_yaml::Value::String(biz_name.clone()),
1929 );
1930 }
1931
1932 if let Some(ref phys_name) = column.physical_name {
1934 prop.insert(
1935 serde_yaml::Value::String("physicalName".to_string()),
1936 serde_yaml::Value::String(phys_name.clone()),
1937 );
1938 }
1939
1940 if let Some(ref opts) = column.logical_type_options
1942 && !opts.is_empty()
1943 {
1944 let mut opts_map = serde_yaml::Mapping::new();
1945 if let Some(min_len) = opts.min_length {
1946 opts_map.insert(
1947 serde_yaml::Value::String("minLength".to_string()),
1948 serde_yaml::Value::Number(serde_yaml::Number::from(min_len)),
1949 );
1950 }
1951 if let Some(max_len) = opts.max_length {
1952 opts_map.insert(
1953 serde_yaml::Value::String("maxLength".to_string()),
1954 serde_yaml::Value::Number(serde_yaml::Number::from(max_len)),
1955 );
1956 }
1957 if let Some(ref pattern) = opts.pattern {
1958 opts_map.insert(
1959 serde_yaml::Value::String("pattern".to_string()),
1960 serde_yaml::Value::String(pattern.clone()),
1961 );
1962 }
1963 if let Some(ref format) = opts.format {
1964 opts_map.insert(
1965 serde_yaml::Value::String("format".to_string()),
1966 serde_yaml::Value::String(format.clone()),
1967 );
1968 }
1969 if let Some(ref minimum) = opts.minimum {
1970 opts_map.insert(
1971 serde_yaml::Value::String("minimum".to_string()),
1972 Self::json_to_yaml_value(minimum),
1973 );
1974 }
1975 if let Some(ref maximum) = opts.maximum {
1976 opts_map.insert(
1977 serde_yaml::Value::String("maximum".to_string()),
1978 Self::json_to_yaml_value(maximum),
1979 );
1980 }
1981 if let Some(ref exc_min) = opts.exclusive_minimum {
1982 opts_map.insert(
1983 serde_yaml::Value::String("exclusiveMinimum".to_string()),
1984 Self::json_to_yaml_value(exc_min),
1985 );
1986 }
1987 if let Some(ref exc_max) = opts.exclusive_maximum {
1988 opts_map.insert(
1989 serde_yaml::Value::String("exclusiveMaximum".to_string()),
1990 Self::json_to_yaml_value(exc_max),
1991 );
1992 }
1993 if let Some(precision) = opts.precision {
1994 opts_map.insert(
1995 serde_yaml::Value::String("precision".to_string()),
1996 serde_yaml::Value::Number(serde_yaml::Number::from(precision)),
1997 );
1998 }
1999 if let Some(scale) = opts.scale {
2000 opts_map.insert(
2001 serde_yaml::Value::String("scale".to_string()),
2002 serde_yaml::Value::Number(serde_yaml::Number::from(scale)),
2003 );
2004 }
2005 if !opts_map.is_empty() {
2006 prop.insert(
2007 serde_yaml::Value::String("logicalTypeOptions".to_string()),
2008 serde_yaml::Value::Mapping(opts_map),
2009 );
2010 }
2011 }
2012
2013 if let Some(pk_pos) = column.primary_key_position {
2015 prop.insert(
2016 serde_yaml::Value::String("primaryKeyPosition".to_string()),
2017 serde_yaml::Value::Number(serde_yaml::Number::from(pk_pos)),
2018 );
2019 }
2020
2021 if column.unique {
2023 prop.insert(
2024 serde_yaml::Value::String("unique".to_string()),
2025 serde_yaml::Value::Bool(true),
2026 );
2027 }
2028
2029 if column.partitioned {
2031 prop.insert(
2032 serde_yaml::Value::String("partitioned".to_string()),
2033 serde_yaml::Value::Bool(true),
2034 );
2035 }
2036
2037 if let Some(part_pos) = column.partition_key_position {
2039 prop.insert(
2040 serde_yaml::Value::String("partitionKeyPosition".to_string()),
2041 serde_yaml::Value::Number(serde_yaml::Number::from(part_pos)),
2042 );
2043 }
2044
2045 if let Some(ref class) = column.classification {
2047 prop.insert(
2048 serde_yaml::Value::String("classification".to_string()),
2049 serde_yaml::Value::String(class.clone()),
2050 );
2051 }
2052
2053 if column.critical_data_element {
2055 prop.insert(
2056 serde_yaml::Value::String("criticalDataElement".to_string()),
2057 serde_yaml::Value::Bool(true),
2058 );
2059 }
2060
2061 if let Some(ref enc_name) = column.encrypted_name {
2063 prop.insert(
2064 serde_yaml::Value::String("encryptedName".to_string()),
2065 serde_yaml::Value::String(enc_name.clone()),
2066 );
2067 }
2068
2069 if !column.transform_source_objects.is_empty() {
2071 let sources: Vec<serde_yaml::Value> = column
2072 .transform_source_objects
2073 .iter()
2074 .map(|s| serde_yaml::Value::String(s.clone()))
2075 .collect();
2076 prop.insert(
2077 serde_yaml::Value::String("transformSourceObjects".to_string()),
2078 serde_yaml::Value::Sequence(sources),
2079 );
2080 }
2081
2082 if let Some(ref logic) = column.transform_logic {
2084 prop.insert(
2085 serde_yaml::Value::String("transformLogic".to_string()),
2086 serde_yaml::Value::String(logic.clone()),
2087 );
2088 }
2089
2090 if let Some(ref desc) = column.transform_description {
2092 prop.insert(
2093 serde_yaml::Value::String("transformDescription".to_string()),
2094 serde_yaml::Value::String(desc.clone()),
2095 );
2096 }
2097
2098 if !column.examples.is_empty() {
2100 let examples: Vec<serde_yaml::Value> = column
2101 .examples
2102 .iter()
2103 .map(Self::json_to_yaml_value)
2104 .collect();
2105 prop.insert(
2106 serde_yaml::Value::String("examples".to_string()),
2107 serde_yaml::Value::Sequence(examples),
2108 );
2109 }
2110
2111 if !column.authoritative_definitions.is_empty() {
2113 let defs: Vec<serde_yaml::Value> = column
2114 .authoritative_definitions
2115 .iter()
2116 .map(|d| {
2117 let mut def_map = serde_yaml::Mapping::new();
2118 def_map.insert(
2119 serde_yaml::Value::String("type".to_string()),
2120 serde_yaml::Value::String(d.definition_type.clone()),
2121 );
2122 def_map.insert(
2123 serde_yaml::Value::String("url".to_string()),
2124 serde_yaml::Value::String(d.url.clone()),
2125 );
2126 serde_yaml::Value::Mapping(def_map)
2127 })
2128 .collect();
2129 prop.insert(
2130 serde_yaml::Value::String("authoritativeDefinitions".to_string()),
2131 serde_yaml::Value::Sequence(defs),
2132 );
2133 }
2134
2135 if !column.tags.is_empty() {
2137 let tags: Vec<serde_yaml::Value> = column
2138 .tags
2139 .iter()
2140 .map(|t| serde_yaml::Value::String(t.clone()))
2141 .collect();
2142 prop.insert(
2143 serde_yaml::Value::String("tags".to_string()),
2144 serde_yaml::Value::Sequence(tags),
2145 );
2146 }
2147
2148 if !column.custom_properties.is_empty() {
2150 let custom_props: Vec<serde_yaml::Value> = column
2151 .custom_properties
2152 .iter()
2153 .map(|(key, value)| {
2154 let mut prop_map = serde_yaml::Mapping::new();
2155 prop_map.insert(
2156 serde_yaml::Value::String("property".to_string()),
2157 serde_yaml::Value::String(key.clone()),
2158 );
2159 prop_map.insert(
2160 serde_yaml::Value::String("value".to_string()),
2161 Self::json_to_yaml_value(value),
2162 );
2163 serde_yaml::Value::Mapping(prop_map)
2164 })
2165 .collect();
2166 prop.insert(
2167 serde_yaml::Value::String("customProperties".to_string()),
2168 serde_yaml::Value::Sequence(custom_props),
2169 );
2170 }
2171
2172 if let Some(ref fk) = column.foreign_key {
2174 let mut fk_map = serde_yaml::Mapping::new();
2175 fk_map.insert(
2176 serde_yaml::Value::String("table".to_string()),
2177 serde_yaml::Value::String(fk.table_id.clone()),
2178 );
2179 fk_map.insert(
2180 serde_yaml::Value::String("column".to_string()),
2181 serde_yaml::Value::String(fk.column_name.clone()),
2182 );
2183 prop.insert(
2184 serde_yaml::Value::String("foreignKey".to_string()),
2185 serde_yaml::Value::Mapping(fk_map),
2186 );
2187 }
2188
2189 let id = column
2192 .name
2193 .chars()
2194 .map(|c| {
2195 if c.is_alphanumeric() {
2196 c.to_lowercase().to_string()
2197 } else {
2198 "_".to_string()
2199 }
2200 })
2201 .collect::<String>()
2202 .replace("__", "_");
2203
2204 prop.insert(
2205 serde_yaml::Value::String("id".to_string()),
2206 serde_yaml::Value::String(format!("{}_obj", id)),
2207 );
2208 prop.insert(
2209 serde_yaml::Value::String("name".to_string()),
2210 serde_yaml::Value::String(column.name.clone()),
2211 );
2212
2213 properties.push(serde_yaml::Value::Mapping(prop));
2214 }
2215
2216 schema_obj.insert(
2217 serde_yaml::Value::String("properties".to_string()),
2218 serde_yaml::Value::Sequence(properties),
2219 );
2220
2221 schema_array.push(serde_yaml::Value::Mapping(schema_obj));
2222 yaml.insert(
2223 serde_yaml::Value::String("schema".to_string()),
2224 serde_yaml::Value::Sequence(schema_array),
2225 );
2226
2227 if !table.quality.is_empty() {
2229 let quality_array: Vec<serde_yaml::Value> = table
2230 .quality
2231 .iter()
2232 .map(|rule| {
2233 let mut rule_map = serde_yaml::Mapping::new();
2234 for (k, v) in rule {
2235 rule_map.insert(
2236 serde_yaml::Value::String(k.clone()),
2237 Self::json_to_yaml_value(v),
2238 );
2239 }
2240 serde_yaml::Value::Mapping(rule_map)
2241 })
2242 .collect();
2243 yaml.insert(
2244 serde_yaml::Value::String("quality".to_string()),
2245 serde_yaml::Value::Sequence(quality_array),
2246 );
2247 }
2248
2249 let excluded_keys = [
2251 "id",
2252 "version",
2253 "status",
2254 "domain",
2255 "dataProduct",
2256 "tenant",
2257 "description",
2258 "team",
2259 "roles",
2260 "pricing",
2261 "terms",
2262 "servers",
2263 "servicelevels",
2264 "links",
2265 "apiVersion",
2266 "kind",
2267 "info",
2268 "dataContractSpecification",
2269 ];
2270
2271 let mut custom_props = Vec::new();
2272 for (key, value) in &table.odcl_metadata {
2273 if !excluded_keys.contains(&key.as_str()) && !value.is_null() {
2274 let mut prop = serde_yaml::Mapping::new();
2275 prop.insert(
2276 serde_yaml::Value::String("property".to_string()),
2277 serde_yaml::Value::String(key.clone()),
2278 );
2279 prop.insert(
2280 serde_yaml::Value::String("value".to_string()),
2281 Self::json_to_yaml_value(value),
2282 );
2283 custom_props.push(serde_yaml::Value::Mapping(prop));
2284 }
2285 }
2286
2287 if let Some(ref db_type) = table.database_type {
2289 let mut prop = serde_yaml::Mapping::new();
2290 prop.insert(
2291 serde_yaml::Value::String("property".to_string()),
2292 serde_yaml::Value::String("databaseType".to_string()),
2293 );
2294 prop.insert(
2295 serde_yaml::Value::String("value".to_string()),
2296 serde_yaml::Value::String(format!("{:?}", db_type)),
2297 );
2298 custom_props.push(serde_yaml::Value::Mapping(prop));
2299 }
2300
2301 if !table.medallion_layers.is_empty() {
2303 let layers: Vec<serde_yaml::Value> = table
2304 .medallion_layers
2305 .iter()
2306 .map(|l| serde_yaml::Value::String(format!("{:?}", l)))
2307 .collect();
2308 let mut prop = serde_yaml::Mapping::new();
2309 prop.insert(
2310 serde_yaml::Value::String("property".to_string()),
2311 serde_yaml::Value::String("medallionLayers".to_string()),
2312 );
2313 prop.insert(
2314 serde_yaml::Value::String("value".to_string()),
2315 serde_yaml::Value::Sequence(layers),
2316 );
2317 custom_props.push(serde_yaml::Value::Mapping(prop));
2318 }
2319
2320 if let Some(ref scd_pattern) = table.scd_pattern {
2322 let mut prop = serde_yaml::Mapping::new();
2323 prop.insert(
2324 serde_yaml::Value::String("property".to_string()),
2325 serde_yaml::Value::String("scdPattern".to_string()),
2326 );
2327 prop.insert(
2328 serde_yaml::Value::String("value".to_string()),
2329 serde_yaml::Value::String(format!("{:?}", scd_pattern)),
2330 );
2331 custom_props.push(serde_yaml::Value::Mapping(prop));
2332 }
2333
2334 if let Some(ref dv_class) = table.data_vault_classification {
2336 let mut prop = serde_yaml::Mapping::new();
2337 prop.insert(
2338 serde_yaml::Value::String("property".to_string()),
2339 serde_yaml::Value::String("dataVaultClassification".to_string()),
2340 );
2341 prop.insert(
2342 serde_yaml::Value::String("value".to_string()),
2343 serde_yaml::Value::String(format!("{:?}", dv_class)),
2344 );
2345 custom_props.push(serde_yaml::Value::Mapping(prop));
2346 }
2347
2348 if let Some(ref catalog) = table.catalog_name {
2350 let mut prop = serde_yaml::Mapping::new();
2351 prop.insert(
2352 serde_yaml::Value::String("property".to_string()),
2353 serde_yaml::Value::String("catalogName".to_string()),
2354 );
2355 prop.insert(
2356 serde_yaml::Value::String("value".to_string()),
2357 serde_yaml::Value::String(catalog.clone()),
2358 );
2359 custom_props.push(serde_yaml::Value::Mapping(prop));
2360 }
2361
2362 if let Some(ref schema) = table.schema_name {
2363 let mut prop = serde_yaml::Mapping::new();
2364 prop.insert(
2365 serde_yaml::Value::String("property".to_string()),
2366 serde_yaml::Value::String("schemaName".to_string()),
2367 );
2368 prop.insert(
2369 serde_yaml::Value::String("value".to_string()),
2370 serde_yaml::Value::String(schema.clone()),
2371 );
2372 custom_props.push(serde_yaml::Value::Mapping(prop));
2373 }
2374
2375 if !custom_props.is_empty() {
2376 yaml.insert(
2377 serde_yaml::Value::String("customProperties".to_string()),
2378 serde_yaml::Value::Sequence(custom_props),
2379 );
2380 }
2381
2382 yaml.insert(
2384 serde_yaml::Value::String("contractCreatedTs".to_string()),
2385 serde_yaml::Value::String(table.created_at.to_rfc3339()),
2386 );
2387
2388 serde_yaml::to_string(&yaml).unwrap_or_default()
2389 }
2390
2391 pub fn export(
2393 &self,
2394 tables: &[Table],
2395 _format: &str,
2396 ) -> Result<HashMap<String, ExportResult>, ExportError> {
2397 let mut exports = HashMap::new();
2398 for table in tables {
2399 let yaml = Self::export_odcs_v3_1_0_format(table);
2401
2402 #[cfg(feature = "schema-validation")]
2404 {
2405 use crate::validation::schema::validate_odcs_internal;
2406 validate_odcs_internal(&yaml).map_err(|e| {
2407 ExportError::ValidationError(format!("ODCS validation failed: {}", e))
2408 })?;
2409 }
2410
2411 exports.insert(
2412 table.name.clone(),
2413 ExportResult {
2414 content: yaml,
2415 format: "odcs_v3_1_0".to_string(),
2416 },
2417 );
2418 }
2419 Ok(exports)
2420 }
2421
2422 pub fn export_model(
2424 model: &DataModel,
2425 table_ids: Option<&[uuid::Uuid]>,
2426 _format: &str,
2427 ) -> HashMap<String, String> {
2428 let tables_to_export: Vec<&Table> = if let Some(ids) = table_ids {
2429 model
2430 .tables
2431 .iter()
2432 .filter(|t| ids.contains(&t.id))
2433 .collect()
2434 } else {
2435 model.tables.iter().collect()
2436 };
2437
2438 let mut exports = HashMap::new();
2439 for table in tables_to_export {
2440 let yaml = Self::export_odcs_v3_1_0_format(table);
2442 exports.insert(table.name.clone(), yaml);
2443 }
2444
2445 exports
2446 }
2447}
2448
2449#[cfg(test)]
2450mod tests {
2451 use super::*;
2452 use crate::models::{Column, Tag};
2453
2454 #[test]
2455 fn test_export_odcs_v3_1_0_basic() {
2456 let table = Table {
2457 id: Table::generate_id("test_table", None, None, None),
2458 name: "test_table".to_string(),
2459 columns: vec![Column {
2460 name: "id".to_string(),
2461 data_type: "BIGINT".to_string(),
2462 nullable: false,
2463 primary_key: true,
2464 description: "Primary key".to_string(),
2465 ..Default::default()
2466 }],
2467 database_type: None,
2468 catalog_name: None,
2469 schema_name: None,
2470 medallion_layers: Vec::new(),
2471 scd_pattern: None,
2472 data_vault_classification: None,
2473 modeling_level: None,
2474 tags: vec![Tag::Simple("test".to_string())],
2475 odcl_metadata: HashMap::new(),
2476 owner: None,
2477 sla: None,
2478 contact_details: None,
2479 infrastructure_type: None,
2480 notes: None,
2481 position: None,
2482 yaml_file_path: None,
2483 drawio_cell_id: None,
2484 quality: Vec::new(),
2485 errors: Vec::new(),
2486 created_at: chrono::Utc::now(),
2487 updated_at: chrono::Utc::now(),
2488 };
2489
2490 let yaml = ODCSExporter::export_table(&table, "odcs_v3_1_0");
2491
2492 assert!(yaml.contains("apiVersion: v3.1.0"));
2493 assert!(yaml.contains("kind: DataContract"));
2494 assert!(yaml.contains("name: test_table"));
2495 assert!(yaml.contains("tags:"));
2496 assert!(yaml.contains("- test"));
2497 }
2498}