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