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