1#[cfg(feature = "bpmn")]
18use crate::import::bpmn::BPMNImporter;
19#[cfg(feature = "dmn")]
20use crate::import::dmn::DMNImporter;
21#[cfg(feature = "openapi")]
22use crate::import::openapi::OpenAPIImporter;
23use crate::import::{cads::CADSImporter, odcs::ODCSImporter, odps::ODPSImporter};
24#[cfg(feature = "bpmn")]
25use crate::models::bpmn::BPMNModel;
26#[cfg(feature = "dmn")]
27use crate::models::dmn::DMNModel;
28#[cfg(feature = "openapi")]
29use crate::models::openapi::{OpenAPIFormat, OpenAPIModel};
30use crate::models::{cads::CADSAsset, domain::Domain, odps::ODPSDataProduct, table::Table};
31use crate::storage::{StorageBackend, StorageError};
32use anyhow::Result;
33use serde::{Deserialize, Serialize};
34use serde_yaml;
35use std::collections::HashMap;
36use tracing::{info, warn};
37use uuid::Uuid;
38
39pub struct ModelLoader<B: StorageBackend> {
41 storage: B,
42}
43
44impl<B: StorageBackend> ModelLoader<B> {
45 pub fn new(storage: B) -> Self {
47 Self { storage }
48 }
49
50 pub async fn load_model(&self, workspace_path: &str) -> Result<ModelLoadResult, StorageError> {
61 self.load_model_from_files(workspace_path).await
63 }
64
65 async fn load_model_from_files(
67 &self,
68 workspace_path: &str,
69 ) -> Result<ModelLoadResult, StorageError> {
70 let tables_dir = format!("{}/tables", workspace_path);
71
72 if !self.storage.dir_exists(&tables_dir).await? {
74 self.storage.create_dir(&tables_dir).await?;
75 }
76
77 let mut tables = Vec::new();
79 let mut table_ids: HashMap<Uuid, String> = HashMap::new();
80
81 let files = self.storage.list_files(&tables_dir).await?;
82 for file_name in files {
83 if file_name.ends_with(".yaml") || file_name.ends_with(".yml") {
84 let file_path = format!("{}/{}", tables_dir, file_name);
85 match self.load_table_from_yaml(&file_path, workspace_path).await {
86 Ok(table_data) => {
87 table_ids.insert(table_data.id, table_data.name.clone());
88 tables.push(table_data);
89 }
90 Err(e) => {
91 warn!("Failed to load table from {}: {}", file_path, e);
92 }
93 }
94 }
95 }
96
97 info!(
98 "Loaded {} tables from workspace {}",
99 tables.len(),
100 workspace_path
101 );
102
103 let relationships_file = format!("{}/relationships.yaml", workspace_path);
105 let mut relationships = Vec::new();
106 let mut orphaned_relationships = Vec::new();
107
108 if self.storage.file_exists(&relationships_file).await? {
109 match self.load_relationships_from_yaml(&relationships_file).await {
110 Ok(loaded_rels) => {
111 for rel in loaded_rels {
113 let source_exists = table_ids.contains_key(&rel.source_table_id);
114 let target_exists = table_ids.contains_key(&rel.target_table_id);
115
116 if source_exists && target_exists {
117 relationships.push(rel.clone());
118 } else {
119 orphaned_relationships.push(rel.clone());
120 warn!(
121 "Orphaned relationship {}: source={} (exists: {}), target={} (exists: {})",
122 rel.id,
123 rel.source_table_id,
124 source_exists,
125 rel.target_table_id,
126 target_exists
127 );
128 }
129 }
130 }
131 Err(e) => {
132 warn!(
133 "Failed to load relationships from {}: {}",
134 relationships_file, e
135 );
136 }
137 }
138 }
139
140 info!(
141 "Loaded {} relationships ({} orphaned) from workspace {}",
142 relationships.len(),
143 orphaned_relationships.len(),
144 workspace_path
145 );
146
147 Ok(ModelLoadResult {
148 tables,
149 relationships,
150 orphaned_relationships,
151 })
152 }
153
154 async fn load_table_from_yaml(
159 &self,
160 yaml_path: &str,
161 workspace_path: &str,
162 ) -> Result<TableData, StorageError> {
163 let content = self.storage.read_file(yaml_path).await?;
164 let yaml_content = String::from_utf8(content)
165 .map_err(|e| StorageError::SerializationError(format!("Invalid UTF-8: {}", e)))?;
166
167 let mut importer = crate::import::odcs::ODCSImporter::new();
169 let (table, parse_errors) = importer.parse_table(&yaml_content).map_err(|e| {
170 StorageError::SerializationError(format!("Failed to parse ODCS YAML: {}", e))
171 })?;
172
173 if !parse_errors.is_empty() {
175 warn!(
176 "Table '{}' parsed with {} warnings/errors",
177 table.name,
178 parse_errors.len()
179 );
180 }
181
182 let relative_path = yaml_path
184 .strip_prefix(workspace_path)
185 .map(|s| s.strip_prefix('/').unwrap_or(s).to_string())
186 .unwrap_or_else(|| yaml_path.to_string());
187
188 Ok(TableData {
189 id: table.id,
190 name: table.name,
191 yaml_file_path: Some(relative_path),
192 yaml_content,
193 })
194 }
195
196 async fn load_relationships_from_yaml(
198 &self,
199 yaml_path: &str,
200 ) -> Result<Vec<RelationshipData>, StorageError> {
201 let content = self.storage.read_file(yaml_path).await?;
202 let yaml_content = String::from_utf8(content)
203 .map_err(|e| StorageError::SerializationError(format!("Invalid UTF-8: {}", e)))?;
204
205 let data: serde_yaml::Value = serde_yaml::from_str(&yaml_content).map_err(|e| {
206 StorageError::SerializationError(format!("Failed to parse YAML: {}", e))
207 })?;
208
209 let mut relationships = Vec::new();
210
211 let rels_array = data
213 .get("relationships")
214 .and_then(|v| v.as_sequence())
215 .or_else(|| data.as_sequence());
216
217 if let Some(rels_array) = rels_array {
218 for rel_data in rels_array {
219 match self.parse_relationship(rel_data) {
220 Ok(rel) => relationships.push(rel),
221 Err(e) => {
222 warn!("Failed to parse relationship: {}", e);
223 }
224 }
225 }
226 }
227
228 Ok(relationships)
229 }
230
231 fn parse_relationship(
233 &self,
234 data: &serde_yaml::Value,
235 ) -> Result<RelationshipData, StorageError> {
236 let source_table_id = data
237 .get("source_table_id")
238 .and_then(|v| v.as_str())
239 .and_then(|s| Uuid::parse_str(s).ok())
240 .ok_or_else(|| {
241 StorageError::SerializationError("Missing source_table_id".to_string())
242 })?;
243
244 let target_table_id = data
245 .get("target_table_id")
246 .and_then(|v| v.as_str())
247 .and_then(|s| Uuid::parse_str(s).ok())
248 .ok_or_else(|| {
249 StorageError::SerializationError("Missing target_table_id".to_string())
250 })?;
251
252 let id = data
254 .get("id")
255 .and_then(|v| v.as_str())
256 .and_then(|s| Uuid::parse_str(s).ok())
257 .unwrap_or_else(|| {
258 crate::models::relationship::Relationship::generate_id(
259 source_table_id,
260 target_table_id,
261 )
262 });
263
264 Ok(RelationshipData {
265 id,
266 source_table_id,
267 target_table_id,
268 })
269 }
270
271 pub async fn load_domains(
278 &self,
279 workspace_path: &str,
280 ) -> Result<DomainLoadResult, StorageError> {
281 let mut domains = Vec::new();
282 let mut tables = HashMap::new();
283 let mut odps_products = HashMap::new();
284 let mut cads_assets = HashMap::new();
285
286 let entries = self.storage.list_files(workspace_path).await?;
294
295 let mut potential_domains = Vec::new();
297 for entry in entries {
298 if !entry.ends_with(".yaml") && !entry.ends_with(".yml") && !entry.contains('.') {
300 potential_domains.push(entry);
301 }
302 }
303
304 for entry in potential_domains {
310 let domain_dir = format!("{}/{}", workspace_path, entry);
311 let domain_yaml_path = format!("{}/domain.yaml", domain_dir);
312
313 if self.storage.file_exists(&domain_yaml_path).await? {
314 match self.load_domain(&domain_dir).await {
316 Ok(domain) => {
317 let domain_name = domain.name.clone();
318 domains.push(domain);
319
320 let domain_tables = self.load_domain_odcs_tables(&domain_dir).await?;
322 let table_count = domain_tables.len();
323 for table in domain_tables {
324 tables.insert(table.id, table);
325 }
326
327 let domain_odps = self.load_domain_odps_products(&domain_dir).await?;
329 let odps_count = domain_odps.len();
330 for product in domain_odps {
331 odps_products.insert(
332 Uuid::parse_str(&product.id).unwrap_or_else(|_| Uuid::new_v4()),
333 product,
334 );
335 }
336
337 let domain_cads = self.load_domain_cads_assets(&domain_dir).await?;
339 let cads_count = domain_cads.len();
340 for asset in domain_cads {
341 cads_assets.insert(
342 Uuid::parse_str(&asset.id).unwrap_or_else(|_| Uuid::new_v4()),
343 asset,
344 );
345 }
346
347 info!(
348 "Loaded domain '{}' with {} tables, {} ODPS products, {} CADS assets",
349 domain_name, table_count, odps_count, cads_count
350 );
351 }
352 Err(e) => {
353 warn!("Failed to load domain from {}: {}", domain_dir, e);
354 }
355 }
356 }
357 }
358
359 info!(
360 "Loaded {} domains from workspace {}",
361 domains.len(),
362 workspace_path
363 );
364
365 Ok(DomainLoadResult {
366 domains,
367 tables,
368 odps_products,
369 cads_assets,
370 })
371 }
372
373 pub async fn load_domains_from_list(
378 &self,
379 workspace_path: &str,
380 domain_directory_names: &[String],
381 ) -> Result<DomainLoadResult, StorageError> {
382 let mut domains = Vec::new();
383 let mut tables = HashMap::new();
384 let mut odps_products = HashMap::new();
385 let mut cads_assets = HashMap::new();
386
387 for domain_dir_name in domain_directory_names {
388 let domain_dir = format!("{}/{}", workspace_path, domain_dir_name);
389 let domain_yaml_path = format!("{}/domain.yaml", domain_dir);
390
391 if self.storage.file_exists(&domain_yaml_path).await? {
392 match self.load_domain(&domain_dir).await {
393 Ok(domain) => {
394 let domain_name = domain.name.clone();
395 domains.push(domain);
396
397 let domain_tables = self.load_domain_odcs_tables(&domain_dir).await?;
399 let table_count = domain_tables.len();
400 for table in domain_tables {
401 tables.insert(table.id, table);
402 }
403
404 let domain_odps = self.load_domain_odps_products(&domain_dir).await?;
406 let odps_count = domain_odps.len();
407 for product in domain_odps {
408 odps_products.insert(
409 Uuid::parse_str(&product.id).unwrap_or_else(|_| Uuid::new_v4()),
410 product,
411 );
412 }
413
414 let domain_cads = self.load_domain_cads_assets(&domain_dir).await?;
416 let cads_count = domain_cads.len();
417 for asset in domain_cads {
418 cads_assets.insert(
419 Uuid::parse_str(&asset.id).unwrap_or_else(|_| Uuid::new_v4()),
420 asset,
421 );
422 }
423
424 info!(
425 "Loaded domain '{}' with {} tables, {} ODPS products, {} CADS assets",
426 domain_name, table_count, odps_count, cads_count
427 );
428 }
429 Err(e) => {
430 warn!("Failed to load domain from {}: {}", domain_dir, e);
431 }
432 }
433 } else {
434 warn!(
435 "Domain directory '{}' does not contain domain.yaml",
436 domain_dir
437 );
438 }
439 }
440
441 info!(
442 "Loaded {} domains from workspace {}",
443 domains.len(),
444 workspace_path
445 );
446
447 Ok(DomainLoadResult {
448 domains,
449 tables,
450 odps_products,
451 cads_assets,
452 })
453 }
454
455 async fn load_domain(&self, domain_dir: &str) -> Result<Domain, StorageError> {
457 let domain_yaml_path = format!("{}/domain.yaml", domain_dir);
458 let content = self.storage.read_file(&domain_yaml_path).await?;
459 let yaml_content = String::from_utf8(content)
460 .map_err(|e| StorageError::SerializationError(format!("Invalid UTF-8: {}", e)))?;
461
462 Domain::from_yaml(&yaml_content).map_err(|e| {
463 StorageError::SerializationError(format!("Failed to parse domain YAML: {}", e))
464 })
465 }
466
467 async fn load_domain_odcs_tables(&self, domain_dir: &str) -> Result<Vec<Table>, StorageError> {
469 let mut tables = Vec::new();
470 let files = self.storage.list_files(domain_dir).await?;
471
472 for file_name in files {
473 if file_name.ends_with(".odcs.yaml") || file_name.ends_with(".odcs.yml") {
474 let file_path = format!("{}/{}", domain_dir, file_name);
475 match self.load_table_from_yaml(&file_path, domain_dir).await {
476 Ok(table_data) => {
477 let mut importer = ODCSImporter::new();
479 match importer.parse_table(&table_data.yaml_content) {
480 Ok((table, _parse_errors)) => {
481 tables.push(table);
482 }
483 Err(e) => {
484 warn!("Failed to parse ODCS table from {}: {}", file_path, e);
485 }
486 }
487 }
488 Err(e) => {
489 warn!("Failed to load ODCS table from {}: {}", file_path, e);
490 }
491 }
492 }
493 }
494
495 Ok(tables)
496 }
497
498 async fn load_domain_odps_products(
500 &self,
501 domain_dir: &str,
502 ) -> Result<Vec<ODPSDataProduct>, StorageError> {
503 let mut products = Vec::new();
504 let files = self.storage.list_files(domain_dir).await?;
505
506 for file_name in files {
507 if file_name.ends_with(".odps.yaml") || file_name.ends_with(".odps.yml") {
508 let file_path = format!("{}/{}", domain_dir, file_name);
509 let content = self.storage.read_file(&file_path).await?;
510 let yaml_content = String::from_utf8(content).map_err(|e| {
511 StorageError::SerializationError(format!("Invalid UTF-8: {}", e))
512 })?;
513
514 let importer = ODPSImporter::new();
515 match importer.import(&yaml_content) {
516 Ok(product) => {
517 products.push(product);
518 }
519 Err(e) => {
520 warn!("Failed to parse ODPS product from {}: {}", file_path, e);
521 }
522 }
523 }
524 }
525
526 Ok(products)
527 }
528
529 async fn load_domain_cads_assets(
531 &self,
532 domain_dir: &str,
533 ) -> Result<Vec<CADSAsset>, StorageError> {
534 let mut assets = Vec::new();
535 let files = self.storage.list_files(domain_dir).await?;
536
537 for file_name in files {
538 if file_name.ends_with(".cads.yaml") || file_name.ends_with(".cads.yml") {
539 let file_path = format!("{}/{}", domain_dir, file_name);
540 let content = self.storage.read_file(&file_path).await?;
541 let yaml_content = String::from_utf8(content).map_err(|e| {
542 StorageError::SerializationError(format!("Invalid UTF-8: {}", e))
543 })?;
544
545 let importer = CADSImporter::new();
546 match importer.import(&yaml_content) {
547 Ok(asset) => {
548 assets.push(asset);
549 }
550 Err(e) => {
551 warn!("Failed to parse CADS asset from {}: {}", file_path, e);
552 }
553 }
554 }
555 }
556
557 Ok(assets)
558 }
559
560 #[cfg(feature = "bpmn")]
562 pub async fn load_bpmn_models(
563 &self,
564 workspace_path: &str,
565 domain_name: &str,
566 ) -> Result<Vec<BPMNModel>, StorageError> {
567 let sanitized_domain_name = sanitize_filename(domain_name);
568 let domain_dir = format!("{}/{}", workspace_path, sanitized_domain_name);
569
570 if !self.storage.dir_exists(&domain_dir).await? {
571 return Ok(Vec::new());
572 }
573
574 let mut models = Vec::new();
575 let files = self.storage.list_files(&domain_dir).await?;
576
577 for file_name in files {
578 if file_name.ends_with(".bpmn.xml") {
579 let file_path = format!("{}/{}", domain_dir, file_name);
580 match self.load_bpmn_model(&domain_dir, &file_name).await {
581 Ok(model) => models.push(model),
582 Err(e) => {
583 warn!("Failed to load BPMN model from {}: {}", file_path, e);
584 }
585 }
586 }
587 }
588
589 Ok(models)
590 }
591
592 #[cfg(feature = "bpmn")]
594 pub async fn load_bpmn_model(
595 &self,
596 domain_dir: &str,
597 file_name: &str,
598 ) -> Result<BPMNModel, StorageError> {
599 let file_path = format!("{}/{}", domain_dir, file_name);
600 let content = self.storage.read_file(&file_path).await?;
601 let xml_content = String::from_utf8(content)
602 .map_err(|e| StorageError::SerializationError(format!("Invalid UTF-8: {}", e)))?;
603
604 let model_name = file_name
606 .strip_suffix(".bpmn.xml")
607 .unwrap_or(file_name)
608 .to_string();
609
610 let domain_id = Uuid::new_v4(); let mut importer = BPMNImporter::new();
616 let model = importer
617 .import(&xml_content, domain_id, Some(&model_name))
618 .map_err(|e| {
619 StorageError::SerializationError(format!("Failed to import BPMN model: {}", e))
620 })?;
621
622 Ok(model)
623 }
624
625 #[cfg(feature = "bpmn")]
627 pub async fn load_bpmn_xml(
628 &self,
629 workspace_path: &str,
630 domain_name: &str,
631 model_name: &str,
632 ) -> Result<String, StorageError> {
633 let sanitized_domain_name = sanitize_filename(domain_name);
634 let sanitized_model_name = sanitize_filename(model_name);
635 let domain_dir = format!("{}/{}", workspace_path, sanitized_domain_name);
636 let file_path = format!("{}/{}.bpmn.xml", domain_dir, sanitized_model_name);
637
638 let content = self.storage.read_file(&file_path).await?;
639 String::from_utf8(content)
640 .map_err(|e| StorageError::SerializationError(format!("Invalid UTF-8: {}", e)))
641 }
642
643 #[cfg(feature = "dmn")]
645 pub async fn load_dmn_models(
646 &self,
647 workspace_path: &str,
648 domain_name: &str,
649 ) -> Result<Vec<DMNModel>, StorageError> {
650 let sanitized_domain_name = sanitize_filename(domain_name);
651 let domain_dir = format!("{}/{}", workspace_path, sanitized_domain_name);
652
653 if !self.storage.dir_exists(&domain_dir).await? {
654 return Ok(Vec::new());
655 }
656
657 let mut models = Vec::new();
658 let files = self.storage.list_files(&domain_dir).await?;
659
660 for file_name in files {
661 if file_name.ends_with(".dmn.xml") {
662 let file_path = format!("{}/{}", domain_dir, file_name);
663 match self.load_dmn_model(&domain_dir, &file_name).await {
664 Ok(model) => models.push(model),
665 Err(e) => {
666 warn!("Failed to load DMN model from {}: {}", file_path, e);
667 }
668 }
669 }
670 }
671
672 Ok(models)
673 }
674
675 #[cfg(feature = "dmn")]
677 pub async fn load_dmn_model(
678 &self,
679 domain_dir: &str,
680 file_name: &str,
681 ) -> Result<DMNModel, StorageError> {
682 let file_path = format!("{}/{}", domain_dir, file_name);
683 let content = self.storage.read_file(&file_path).await?;
684 let xml_content = String::from_utf8(content)
685 .map_err(|e| StorageError::SerializationError(format!("Invalid UTF-8: {}", e)))?;
686
687 let model_name = file_name
689 .strip_suffix(".dmn.xml")
690 .unwrap_or(file_name)
691 .to_string();
692
693 let domain_id = Uuid::new_v4(); let mut importer = DMNImporter::new();
699 let model = importer
700 .import(&xml_content, domain_id, Some(&model_name))
701 .map_err(|e| {
702 StorageError::SerializationError(format!("Failed to import DMN model: {}", e))
703 })?;
704
705 Ok(model)
706 }
707
708 #[cfg(feature = "dmn")]
710 pub async fn load_dmn_xml(
711 &self,
712 workspace_path: &str,
713 domain_name: &str,
714 model_name: &str,
715 ) -> Result<String, StorageError> {
716 let sanitized_domain_name = sanitize_filename(domain_name);
717 let sanitized_model_name = sanitize_filename(model_name);
718 let domain_dir = format!("{}/{}", workspace_path, sanitized_domain_name);
719 let file_path = format!("{}/{}.dmn.xml", domain_dir, sanitized_model_name);
720
721 let content = self.storage.read_file(&file_path).await?;
722 String::from_utf8(content)
723 .map_err(|e| StorageError::SerializationError(format!("Invalid UTF-8: {}", e)))
724 }
725
726 #[cfg(feature = "openapi")]
728 pub async fn load_openapi_models(
729 &self,
730 workspace_path: &str,
731 domain_name: &str,
732 ) -> Result<Vec<OpenAPIModel>, StorageError> {
733 let sanitized_domain_name = sanitize_filename(domain_name);
734 let domain_dir = format!("{}/{}", workspace_path, sanitized_domain_name);
735
736 if !self.storage.dir_exists(&domain_dir).await? {
737 return Ok(Vec::new());
738 }
739
740 let mut models = Vec::new();
741 let files = self.storage.list_files(&domain_dir).await?;
742
743 for file_name in files {
744 if file_name.ends_with(".openapi.yaml")
745 || file_name.ends_with(".openapi.yml")
746 || file_name.ends_with(".openapi.json")
747 {
748 let file_path = format!("{}/{}", domain_dir, file_name);
749 match self.load_openapi_model(&domain_dir, &file_name).await {
750 Ok(model) => models.push(model),
751 Err(e) => {
752 warn!("Failed to load OpenAPI spec from {}: {}", file_path, e);
753 }
754 }
755 }
756 }
757
758 Ok(models)
759 }
760
761 #[cfg(feature = "openapi")]
763 pub async fn load_openapi_model(
764 &self,
765 domain_dir: &str,
766 file_name: &str,
767 ) -> Result<OpenAPIModel, StorageError> {
768 let file_path = format!("{}/{}", domain_dir, file_name);
769 let content = self.storage.read_file(&file_path).await?;
770 let spec_content = String::from_utf8(content)
771 .map_err(|e| StorageError::SerializationError(format!("Invalid UTF-8: {}", e)))?;
772
773 let api_name = file_name
777 .strip_suffix(".openapi.yaml")
778 .or_else(|| file_name.strip_suffix(".openapi.yml"))
779 .or_else(|| file_name.strip_suffix(".openapi.json"))
780 .unwrap_or(file_name)
781 .to_string();
782
783 let domain_id = Uuid::new_v4(); let mut importer = OpenAPIImporter::new();
789 let model = importer
790 .import(&spec_content, domain_id, Some(&api_name))
791 .map_err(|e| {
792 StorageError::SerializationError(format!("Failed to import OpenAPI spec: {}", e))
793 })?;
794
795 Ok(model)
796 }
797
798 #[cfg(feature = "openapi")]
800 pub async fn load_openapi_content(
801 &self,
802 workspace_path: &str,
803 domain_name: &str,
804 api_name: &str,
805 format: Option<OpenAPIFormat>,
806 ) -> Result<String, StorageError> {
807 let sanitized_domain_name = sanitize_filename(domain_name);
808 let sanitized_api_name = sanitize_filename(api_name);
809 let domain_dir = format!("{}/{}", workspace_path, sanitized_domain_name);
810
811 let extensions: Vec<&str> = if let Some(fmt) = format {
813 match fmt {
814 OpenAPIFormat::Yaml => vec!["yaml", "yml"],
815 OpenAPIFormat::Json => vec!["json"],
816 }
817 } else {
818 vec!["yaml", "yml", "json"]
819 };
820
821 for ext in extensions {
822 let file_path = format!("{}/{}.openapi.{}", domain_dir, sanitized_api_name, ext);
823 if self.storage.file_exists(&file_path).await? {
824 let content = self.storage.read_file(&file_path).await?;
825 return String::from_utf8(content).map_err(|e| {
826 StorageError::SerializationError(format!("Invalid UTF-8: {}", e))
827 });
828 }
829 }
830
831 Err(StorageError::IoError(format!(
832 "OpenAPI spec '{}.openapi.{{yaml,json}}' not found in domain '{}'",
833 api_name, domain_name
834 )))
835 }
836}
837
838fn sanitize_filename(name: &str) -> String {
840 name.chars()
841 .map(|c| match c {
842 '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '_',
843 _ => c,
844 })
845 .collect()
846}
847
848#[derive(Debug, Serialize, Deserialize)]
850pub struct ModelLoadResult {
851 pub tables: Vec<TableData>,
852 pub relationships: Vec<RelationshipData>,
853 pub orphaned_relationships: Vec<RelationshipData>,
854}
855
856#[derive(Debug, Clone, Serialize, Deserialize)]
858pub struct TableData {
859 pub id: Uuid,
860 pub name: String,
861 pub yaml_file_path: Option<String>,
862 pub yaml_content: String,
863}
864
865#[derive(Debug, Clone, Serialize, Deserialize)]
867pub struct RelationshipData {
868 pub id: Uuid,
869 pub source_table_id: Uuid,
870 pub target_table_id: Uuid,
871}
872
873#[derive(Debug)]
875pub struct DomainLoadResult {
876 pub domains: Vec<Domain>,
877 pub tables: HashMap<Uuid, Table>,
878 pub odps_products: HashMap<Uuid, ODPSDataProduct>,
879 pub cads_assets: HashMap<Uuid, CADSAsset>,
880}