1pub mod auth;
12pub mod export;
13#[cfg(feature = "git")]
14pub mod git;
15pub mod import;
16pub mod model;
17pub mod models;
18pub mod storage;
19pub mod validation;
20pub mod workspace;
21
22#[cfg(feature = "api-backend")]
24pub use storage::api::ApiStorageBackend;
25#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
26pub use storage::browser::BrowserStorageBackend;
27#[cfg(feature = "native-fs")]
28pub use storage::filesystem::FileSystemStorageBackend;
29pub use storage::{StorageBackend, StorageError};
30
31#[cfg(feature = "png-export")]
32pub use export::PNGExporter;
33pub use export::{
34 AvroExporter, ExportError, ExportResult, JSONSchemaExporter, ODCSExporter, ProtobufExporter,
35 SQLExporter, dataflow::DataFlowExporter,
36};
37pub use import::{
38 AvroImporter, ImportError, ImportResult, JSONSchemaImporter, ODCSImporter, ProtobufImporter,
39 SQLImporter, dataflow::DataFlowImporter,
40};
41#[cfg(feature = "api-backend")]
42pub use model::ApiModelLoader;
43pub use model::{ModelLoader, ModelSaver};
44pub use validation::{
45 RelationshipValidationError, RelationshipValidationResult, TableValidationError,
46 TableValidationResult,
47};
48
49pub use models::enums::*;
51pub use models::{Column, ContactDetails, DataModel, ForeignKey, Relationship, SlaProperty, Table};
52
53pub use auth::{
55 AuthMode, AuthState, GitHubEmail, InitiateOAuthRequest, InitiateOAuthResponse,
56 SelectEmailRequest,
57};
58
59pub use workspace::{
61 CreateWorkspaceRequest, CreateWorkspaceResponse, ListProfilesResponse, LoadProfileRequest,
62 ProfileInfo, WorkspaceInfo,
63};
64
65#[cfg(feature = "git")]
67pub use git::{GitError, GitService, GitStatus};
68
69#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
71mod wasm {
72 use crate::export::ExportError;
73 use crate::import::{ImportError, ImportResult};
74 use crate::models::DataModel;
75 use js_sys;
76 use serde_json;
77 use serde_yaml;
78 use uuid;
79 use wasm_bindgen::prelude::*;
80 use wasm_bindgen_futures;
81
82 fn import_error_to_js(err: ImportError) -> JsValue {
84 JsValue::from_str(&err.to_string())
85 }
86
87 fn export_error_to_js(err: ExportError) -> JsValue {
89 JsValue::from_str(&err.to_string())
90 }
91
92 fn serialize_import_result(result: &ImportResult) -> Result<String, JsValue> {
94 serde_json::to_string(result)
95 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
96 }
97
98 fn deserialize_workspace(json: &str) -> Result<DataModel, JsValue> {
100 serde_json::from_str(json)
101 .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))
102 }
103
104 #[wasm_bindgen]
114 pub fn parse_odcs_yaml(yaml_content: &str) -> Result<String, JsValue> {
115 let mut importer = crate::import::ODCSImporter::new();
116 match importer.import(yaml_content) {
117 Ok(result) => serialize_import_result(&result),
118 Err(err) => Err(import_error_to_js(err)),
119 }
120 }
121
122 #[wasm_bindgen]
132 pub fn export_to_odcs_yaml(workspace_json: &str) -> Result<String, JsValue> {
133 let model = deserialize_workspace(workspace_json)?;
134
135 let exports = crate::export::ODCSExporter::export_model(&model, None, "odcs_v3_1_0");
137
138 let yaml_docs: Vec<String> = exports.values().cloned().collect();
140 Ok(yaml_docs.join("\n---\n"))
141 }
142
143 #[wasm_bindgen]
154 pub fn import_from_sql(sql_content: &str, dialect: &str) -> Result<String, JsValue> {
155 let importer = crate::import::SQLImporter::new(dialect);
156 match importer.parse(sql_content) {
157 Ok(result) => serialize_import_result(&result),
158 Err(err) => Err(JsValue::from_str(&format!("Parse error: {}", err))),
159 }
160 }
161
162 #[wasm_bindgen]
172 pub fn import_from_avro(avro_content: &str) -> Result<String, JsValue> {
173 let importer = crate::import::AvroImporter::new();
174 match importer.import(avro_content) {
175 Ok(result) => serialize_import_result(&result),
176 Err(err) => Err(import_error_to_js(err)),
177 }
178 }
179
180 #[wasm_bindgen]
190 pub fn import_from_json_schema(json_schema_content: &str) -> Result<String, JsValue> {
191 let importer = crate::import::JSONSchemaImporter::new();
192 match importer.import(json_schema_content) {
193 Ok(result) => serialize_import_result(&result),
194 Err(err) => Err(import_error_to_js(err)),
195 }
196 }
197
198 #[wasm_bindgen]
208 pub fn import_from_protobuf(protobuf_content: &str) -> Result<String, JsValue> {
209 let importer = crate::import::ProtobufImporter::new();
210 match importer.import(protobuf_content) {
211 Ok(result) => serialize_import_result(&result),
212 Err(err) => Err(import_error_to_js(err)),
213 }
214 }
215
216 #[wasm_bindgen]
227 pub fn export_to_sql(workspace_json: &str, dialect: &str) -> Result<String, JsValue> {
228 let model = deserialize_workspace(workspace_json)?;
229 let exporter = crate::export::SQLExporter;
230 match exporter.export(&model.tables, Some(dialect)) {
231 Ok(result) => Ok(result.content),
232 Err(err) => Err(export_error_to_js(err)),
233 }
234 }
235
236 #[wasm_bindgen]
246 pub fn export_to_avro(workspace_json: &str) -> Result<String, JsValue> {
247 let model = deserialize_workspace(workspace_json)?;
248 let exporter = crate::export::AvroExporter;
249 match exporter.export(&model.tables) {
250 Ok(result) => Ok(result.content),
251 Err(err) => Err(export_error_to_js(err)),
252 }
253 }
254
255 #[wasm_bindgen]
265 pub fn export_to_json_schema(workspace_json: &str) -> Result<String, JsValue> {
266 let model = deserialize_workspace(workspace_json)?;
267 let exporter = crate::export::JSONSchemaExporter;
268 match exporter.export(&model.tables) {
269 Ok(result) => Ok(result.content),
270 Err(err) => Err(export_error_to_js(err)),
271 }
272 }
273
274 #[wasm_bindgen]
284 pub fn export_to_protobuf(workspace_json: &str) -> Result<String, JsValue> {
285 let model = deserialize_workspace(workspace_json)?;
286 let exporter = crate::export::ProtobufExporter;
287 match exporter.export(&model.tables) {
288 Ok(result) => Ok(result.content),
289 Err(err) => Err(export_error_to_js(err)),
290 }
291 }
292
293 #[wasm_bindgen]
303 pub fn import_from_dataflow(yaml_content: &str) -> Result<String, JsValue> {
304 let importer = crate::import::dataflow::DataFlowImporter::new();
305 match importer.import(yaml_content) {
306 Ok(model) => serde_json::to_string(&model)
307 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
308 Err(err) => Err(import_error_to_js(err)),
309 }
310 }
311
312 #[wasm_bindgen]
322 pub fn export_to_dataflow(workspace_json: &str) -> Result<String, JsValue> {
323 let model = deserialize_workspace(workspace_json)?;
324 let exporter = crate::export::dataflow::DataFlowExporter::new();
325 Ok(exporter.export_model(&model))
326 }
327
328 #[wasm_bindgen]
339 pub fn filter_nodes_by_owner(workspace_json: &str, owner: &str) -> Result<String, JsValue> {
340 let model = deserialize_workspace(workspace_json)?;
341 let filtered = model.filter_nodes_by_owner(owner);
342 serde_json::to_string(&filtered)
343 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
344 }
345
346 #[wasm_bindgen]
357 pub fn filter_relationships_by_owner(
358 workspace_json: &str,
359 owner: &str,
360 ) -> Result<String, JsValue> {
361 let model = deserialize_workspace(workspace_json)?;
362 let filtered = model.filter_relationships_by_owner(owner);
363 serde_json::to_string(&filtered)
364 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
365 }
366
367 #[wasm_bindgen]
378 pub fn filter_nodes_by_infrastructure_type(
379 workspace_json: &str,
380 infrastructure_type: &str,
381 ) -> Result<String, JsValue> {
382 let model = deserialize_workspace(workspace_json)?;
383 let infra_type: crate::models::enums::InfrastructureType =
384 serde_json::from_str(&format!("\"{}\"", infrastructure_type)).map_err(|e| {
385 JsValue::from_str(&format!(
386 "Invalid infrastructure type '{}': {}",
387 infrastructure_type, e
388 ))
389 })?;
390 let filtered = model.filter_nodes_by_infrastructure_type(infra_type);
391 serde_json::to_string(&filtered)
392 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
393 }
394
395 #[wasm_bindgen]
406 pub fn filter_relationships_by_infrastructure_type(
407 workspace_json: &str,
408 infrastructure_type: &str,
409 ) -> Result<String, JsValue> {
410 let model = deserialize_workspace(workspace_json)?;
411 let infra_type: crate::models::enums::InfrastructureType =
412 serde_json::from_str(&format!("\"{}\"", infrastructure_type)).map_err(|e| {
413 JsValue::from_str(&format!(
414 "Invalid infrastructure type '{}': {}",
415 infrastructure_type, e
416 ))
417 })?;
418 let filtered = model.filter_relationships_by_infrastructure_type(infra_type);
419 serde_json::to_string(&filtered)
420 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
421 }
422
423 #[wasm_bindgen]
434 pub fn filter_by_tags(workspace_json: &str, tag: &str) -> Result<String, JsValue> {
435 let model = deserialize_workspace(workspace_json)?;
436 let (nodes, relationships) = model.filter_by_tags(tag);
437 let result = serde_json::json!({
438 "nodes": nodes,
439 "relationships": relationships
440 });
441 serde_json::to_string(&result)
442 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
443 }
444
445 #[wasm_bindgen]
459 pub fn validate_table_name(name: &str) -> Result<String, JsValue> {
460 match crate::validation::input::validate_table_name(name) {
461 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
462 Err(err) => {
463 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
464 }
465 }
466 }
467
468 #[wasm_bindgen]
478 pub fn validate_column_name(name: &str) -> Result<String, JsValue> {
479 match crate::validation::input::validate_column_name(name) {
480 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
481 Err(err) => {
482 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
483 }
484 }
485 }
486
487 #[wasm_bindgen]
497 pub fn validate_uuid(id: &str) -> Result<String, JsValue> {
498 match crate::validation::input::validate_uuid(id) {
499 Ok(uuid) => {
500 Ok(serde_json::json!({"valid": true, "uuid": uuid.to_string()}).to_string())
501 }
502 Err(err) => {
503 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
504 }
505 }
506 }
507
508 #[wasm_bindgen]
518 pub fn validate_data_type(data_type: &str) -> Result<String, JsValue> {
519 match crate::validation::input::validate_data_type(data_type) {
520 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
521 Err(err) => {
522 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
523 }
524 }
525 }
526
527 #[wasm_bindgen]
537 pub fn validate_description(desc: &str) -> Result<String, JsValue> {
538 match crate::validation::input::validate_description(desc) {
539 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
540 Err(err) => {
541 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
542 }
543 }
544 }
545
546 #[wasm_bindgen]
557 pub fn sanitize_sql_identifier(name: &str, dialect: &str) -> String {
558 crate::validation::input::sanitize_sql_identifier(name, dialect)
559 }
560
561 #[wasm_bindgen]
571 pub fn sanitize_description(desc: &str) -> String {
572 crate::validation::input::sanitize_description(desc)
573 }
574
575 #[wasm_bindgen]
586 pub fn detect_naming_conflicts(
587 existing_tables_json: &str,
588 new_tables_json: &str,
589 ) -> Result<String, JsValue> {
590 let existing_tables: Vec<crate::models::Table> = serde_json::from_str(existing_tables_json)
591 .map_err(|e| JsValue::from_str(&format!("Failed to parse existing tables: {}", e)))?;
592 let new_tables: Vec<crate::models::Table> = serde_json::from_str(new_tables_json)
593 .map_err(|e| JsValue::from_str(&format!("Failed to parse new tables: {}", e)))?;
594
595 let validator = crate::validation::tables::TableValidator::new();
596 let conflicts = validator.detect_naming_conflicts(&existing_tables, &new_tables);
597
598 serde_json::to_string(&conflicts)
599 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
600 }
601
602 #[wasm_bindgen]
612 pub fn validate_pattern_exclusivity(table_json: &str) -> Result<String, JsValue> {
613 let table: crate::models::Table = serde_json::from_str(table_json)
614 .map_err(|e| JsValue::from_str(&format!("Failed to parse table: {}", e)))?;
615
616 let validator = crate::validation::tables::TableValidator::new();
617 match validator.validate_pattern_exclusivity(&table) {
618 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
619 Err(violation) => {
620 Ok(serde_json::json!({"valid": false, "violation": violation}).to_string())
621 }
622 }
623 }
624
625 #[wasm_bindgen]
637 pub fn check_circular_dependency(
638 relationships_json: &str,
639 source_table_id: &str,
640 target_table_id: &str,
641 ) -> Result<String, JsValue> {
642 let relationships: Vec<crate::models::Relationship> =
643 serde_json::from_str(relationships_json)
644 .map_err(|e| JsValue::from_str(&format!("Failed to parse relationships: {}", e)))?;
645
646 let source_id = uuid::Uuid::parse_str(source_table_id)
647 .map_err(|e| JsValue::from_str(&format!("Invalid source_table_id: {}", e)))?;
648 let target_id = uuid::Uuid::parse_str(target_table_id)
649 .map_err(|e| JsValue::from_str(&format!("Invalid target_table_id: {}", e)))?;
650
651 let validator = crate::validation::relationships::RelationshipValidator::new();
652 match validator.check_circular_dependency(&relationships, source_id, target_id) {
653 Ok((has_cycle, cycle_path)) => {
654 let cycle_path_strs: Vec<String> = cycle_path
655 .map(|path| path.iter().map(|id| id.to_string()).collect())
656 .unwrap_or_default();
657 Ok(serde_json::json!({
658 "has_cycle": has_cycle,
659 "cycle_path": cycle_path_strs
660 })
661 .to_string())
662 }
663 Err(err) => Err(JsValue::from_str(&format!("Validation error: {}", err))),
664 }
665 }
666
667 #[wasm_bindgen]
678 pub fn validate_no_self_reference(
679 source_table_id: &str,
680 target_table_id: &str,
681 ) -> Result<String, JsValue> {
682 let source_id = uuid::Uuid::parse_str(source_table_id)
683 .map_err(|e| JsValue::from_str(&format!("Invalid source_table_id: {}", e)))?;
684 let target_id = uuid::Uuid::parse_str(target_table_id)
685 .map_err(|e| JsValue::from_str(&format!("Invalid target_table_id: {}", e)))?;
686
687 let validator = crate::validation::relationships::RelationshipValidator::new();
688 match validator.validate_no_self_reference(source_id, target_id) {
689 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
690 Err(self_ref) => {
691 Ok(serde_json::json!({"valid": false, "self_reference": self_ref}).to_string())
692 }
693 }
694 }
695
696 #[cfg(feature = "png-export")]
712 #[wasm_bindgen]
713 pub fn export_to_png(workspace_json: &str, width: u32, height: u32) -> Result<String, JsValue> {
714 let model = deserialize_workspace(workspace_json)?;
715 let exporter = crate::export::PNGExporter::new();
716 match exporter.export(&model.tables, width, height) {
717 Ok(result) => Ok(result.content), Err(err) => Err(export_error_to_js(err)),
719 }
720 }
721
722 #[wasm_bindgen]
738 pub fn load_model(db_name: &str, store_name: &str, workspace_path: &str) -> js_sys::Promise {
739 let db_name = db_name.to_string();
740 let store_name = store_name.to_string();
741 let workspace_path = workspace_path.to_string();
742
743 wasm_bindgen_futures::future_to_promise(async move {
744 let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
745 let loader = crate::model::ModelLoader::new(storage);
746 match loader.load_model(&workspace_path).await {
747 Ok(result) => serde_json::to_string(&result)
748 .map(|s| JsValue::from_str(&s))
749 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
750 Err(err) => Err(JsValue::from_str(&format!("Storage error: {}", err))),
751 }
752 })
753 }
754
755 #[wasm_bindgen]
768 pub fn save_model(
769 db_name: &str,
770 store_name: &str,
771 workspace_path: &str,
772 model_json: &str,
773 ) -> js_sys::Promise {
774 let db_name = db_name.to_string();
775 let store_name = store_name.to_string();
776 let workspace_path = workspace_path.to_string();
777 let model_json = model_json.to_string();
778
779 wasm_bindgen_futures::future_to_promise(async move {
780 let model: crate::models::DataModel = serde_json::from_str(&model_json)
781 .map_err(|e| JsValue::from_str(&format!("Failed to parse model: {}", e)))?;
782
783 let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
784 let saver = crate::model::ModelSaver::new(storage);
785
786 for table in &model.tables {
789 let yaml = crate::export::ODCSExporter::export_table(table, "odcs_v3_1_0");
791 let table_data = crate::model::saver::TableData {
792 id: table.id,
793 name: table.name.clone(),
794 yaml_file_path: Some(format!("tables/{}.yaml", table.name)),
795 yaml_value: serde_yaml::from_str(&yaml)
796 .map_err(|e| JsValue::from_str(&format!("Failed to parse YAML: {}", e)))?,
797 };
798 saver
799 .save_table(&workspace_path, &table_data)
800 .await
801 .map_err(|e| JsValue::from_str(&format!("Failed to save table: {}", e)))?;
802 }
803
804 if !model.relationships.is_empty() {
806 let rel_data: Vec<crate::model::saver::RelationshipData> = model
807 .relationships
808 .iter()
809 .map(|rel| {
810 let yaml_value = serde_json::json!({
811 "id": rel.id.to_string(),
812 "source_table_id": rel.source_table_id.to_string(),
813 "target_table_id": rel.target_table_id.to_string(),
814 });
815 let yaml_str = serde_json::to_string(&yaml_value)
817 .map_err(|e| format!("Failed to serialize relationship: {}", e))?;
818 let yaml_value = serde_yaml::from_str(&yaml_str)
819 .map_err(|e| format!("Failed to convert to YAML: {}", e))?;
820 Ok(crate::model::saver::RelationshipData {
821 id: rel.id,
822 source_table_id: rel.source_table_id,
823 target_table_id: rel.target_table_id,
824 yaml_value,
825 })
826 })
827 .collect::<Result<Vec<_>, String>>()
828 .map_err(|e| JsValue::from_str(&e))?;
829
830 saver
831 .save_relationships(&workspace_path, &rel_data)
832 .await
833 .map_err(|e| {
834 JsValue::from_str(&format!("Failed to save relationships: {}", e))
835 })?;
836 }
837
838 Ok(JsValue::from_str("Model saved successfully"))
839 })
840 }
841}