1pub mod auth;
12#[cfg(feature = "cli")]
13pub mod cli;
14pub mod convert;
15pub mod export;
16#[cfg(feature = "git")]
17pub mod git;
18pub mod import;
19pub mod model;
20pub mod models;
21pub mod storage;
22pub mod validation;
23pub mod workspace;
24
25#[cfg(feature = "api-backend")]
27pub use storage::api::ApiStorageBackend;
28#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
29pub use storage::browser::BrowserStorageBackend;
30#[cfg(feature = "native-fs")]
31pub use storage::filesystem::FileSystemStorageBackend;
32pub use storage::{StorageBackend, StorageError};
33
34pub use convert::{ConversionError, convert_to_odcs};
35#[cfg(feature = "png-export")]
36pub use export::PNGExporter;
37pub use export::{
38 AvroExporter, ExportError, ExportResult, JSONSchemaExporter, ODCSExporter, ProtobufExporter,
39 SQLExporter,
40};
41pub use import::{
42 AvroImporter, ImportError, ImportResult, JSONSchemaImporter, ODCSImporter, ProtobufImporter,
43 SQLImporter,
44};
45#[cfg(feature = "api-backend")]
46pub use model::ApiModelLoader;
47pub use model::{ModelLoader, ModelSaver};
48pub use validation::{
49 RelationshipValidationError, RelationshipValidationResult, TableValidationError,
50 TableValidationResult,
51};
52
53pub use models::enums::*;
55pub use models::{Column, ContactDetails, DataModel, ForeignKey, Relationship, SlaProperty, Table};
56
57pub use auth::{
59 AuthMode, AuthState, GitHubEmail, InitiateOAuthRequest, InitiateOAuthResponse,
60 SelectEmailRequest,
61};
62
63pub use workspace::{
65 CreateWorkspaceRequest, CreateWorkspaceResponse, ListProfilesResponse, LoadProfileRequest,
66 ProfileInfo, WorkspaceInfo,
67};
68
69#[cfg(feature = "git")]
71pub use git::{GitError, GitService, GitStatus};
72
73#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
75mod wasm {
76 use crate::export::ExportError;
77 use crate::import::{ImportError, ImportResult};
78 use crate::models::DataModel;
79 use js_sys;
80 use serde_json;
81 use serde_yaml;
82 use uuid;
83 use wasm_bindgen::prelude::*;
84 use wasm_bindgen_futures;
85
86 fn import_error_to_js(err: ImportError) -> JsValue {
88 JsValue::from_str(&err.to_string())
89 }
90
91 fn export_error_to_js(err: ExportError) -> JsValue {
93 JsValue::from_str(&err.to_string())
94 }
95
96 fn serialize_import_result(result: &ImportResult) -> Result<String, JsValue> {
98 serde_json::to_string(result)
99 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
100 }
101
102 fn deserialize_workspace(json: &str) -> Result<DataModel, JsValue> {
104 serde_json::from_str(json)
105 .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))
106 }
107
108 #[wasm_bindgen]
118 pub fn parse_odcs_yaml(yaml_content: &str) -> Result<String, JsValue> {
119 let mut importer = crate::import::ODCSImporter::new();
120 match importer.import(yaml_content) {
121 Ok(result) => serialize_import_result(&result),
122 Err(err) => Err(import_error_to_js(err)),
123 }
124 }
125
126 #[wasm_bindgen]
136 pub fn export_to_odcs_yaml(workspace_json: &str) -> Result<String, JsValue> {
137 let model = deserialize_workspace(workspace_json)?;
138
139 let exports = crate::export::ODCSExporter::export_model(&model, None, "odcs_v3_1_0");
141
142 let yaml_docs: Vec<String> = exports.values().cloned().collect();
144 Ok(yaml_docs.join("\n---\n"))
145 }
146
147 #[wasm_bindgen]
158 pub fn import_from_sql(sql_content: &str, dialect: &str) -> Result<String, JsValue> {
159 let importer = crate::import::SQLImporter::new(dialect);
160 match importer.parse(sql_content) {
161 Ok(result) => serialize_import_result(&result),
162 Err(err) => Err(JsValue::from_str(&format!("Parse error: {}", err))),
163 }
164 }
165
166 #[wasm_bindgen]
176 pub fn import_from_avro(avro_content: &str) -> Result<String, JsValue> {
177 let importer = crate::import::AvroImporter::new();
178 match importer.import(avro_content) {
179 Ok(result) => serialize_import_result(&result),
180 Err(err) => Err(import_error_to_js(err)),
181 }
182 }
183
184 #[wasm_bindgen]
194 pub fn import_from_json_schema(json_schema_content: &str) -> Result<String, JsValue> {
195 let importer = crate::import::JSONSchemaImporter::new();
196 match importer.import(json_schema_content) {
197 Ok(result) => serialize_import_result(&result),
198 Err(err) => Err(import_error_to_js(err)),
199 }
200 }
201
202 #[wasm_bindgen]
212 pub fn import_from_protobuf(protobuf_content: &str) -> Result<String, JsValue> {
213 let importer = crate::import::ProtobufImporter::new();
214 match importer.import(protobuf_content) {
215 Ok(result) => serialize_import_result(&result),
216 Err(err) => Err(import_error_to_js(err)),
217 }
218 }
219
220 #[wasm_bindgen]
231 pub fn export_to_sql(workspace_json: &str, dialect: &str) -> Result<String, JsValue> {
232 let model = deserialize_workspace(workspace_json)?;
233 let exporter = crate::export::SQLExporter;
234 match exporter.export(&model.tables, Some(dialect)) {
235 Ok(result) => Ok(result.content),
236 Err(err) => Err(export_error_to_js(err)),
237 }
238 }
239
240 #[wasm_bindgen]
250 pub fn export_to_avro(workspace_json: &str) -> Result<String, JsValue> {
251 let model = deserialize_workspace(workspace_json)?;
252 let exporter = crate::export::AvroExporter;
253 match exporter.export(&model.tables) {
254 Ok(result) => Ok(result.content),
255 Err(err) => Err(export_error_to_js(err)),
256 }
257 }
258
259 #[wasm_bindgen]
269 pub fn export_to_json_schema(workspace_json: &str) -> Result<String, JsValue> {
270 let model = deserialize_workspace(workspace_json)?;
271 let exporter = crate::export::JSONSchemaExporter;
272 match exporter.export(&model.tables) {
273 Ok(result) => Ok(result.content),
274 Err(err) => Err(export_error_to_js(err)),
275 }
276 }
277
278 #[wasm_bindgen]
288 pub fn export_to_protobuf(workspace_json: &str) -> Result<String, JsValue> {
289 let model = deserialize_workspace(workspace_json)?;
290 let exporter = crate::export::ProtobufExporter;
291 match exporter.export(&model.tables) {
292 Ok(result) => Ok(result.content),
293 Err(err) => Err(export_error_to_js(err)),
294 }
295 }
296
297 #[wasm_bindgen]
307 pub fn import_from_cads(yaml_content: &str) -> Result<String, JsValue> {
308 let importer = crate::import::CADSImporter::new();
309 match importer.import(yaml_content) {
310 Ok(asset) => serde_json::to_string(&asset)
311 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
312 Err(err) => Err(import_error_to_js(err)),
313 }
314 }
315
316 #[wasm_bindgen]
326 pub fn export_to_cads(asset_json: &str) -> Result<String, JsValue> {
327 let asset: crate::models::cads::CADSAsset = serde_json::from_str(asset_json)
328 .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
329 let exporter = crate::export::CADSExporter;
330 match exporter.export(&asset) {
331 Ok(yaml) => Ok(yaml),
332 Err(err) => Err(export_error_to_js(err)),
333 }
334 }
335
336 #[wasm_bindgen]
346 pub fn import_from_odps(yaml_content: &str) -> Result<String, JsValue> {
347 let importer = crate::import::ODPSImporter::new();
348 match importer.import(yaml_content) {
349 Ok(product) => serde_json::to_string(&product)
350 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
351 Err(err) => Err(import_error_to_js(err)),
352 }
353 }
354
355 #[wasm_bindgen]
365 pub fn export_to_odps(product_json: &str) -> Result<String, JsValue> {
366 let product: crate::models::odps::ODPSDataProduct = serde_json::from_str(product_json)
367 .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
368 let exporter = crate::export::ODPSExporter;
369 match exporter.export(&product) {
370 Ok(yaml) => Ok(yaml),
371 Err(err) => Err(export_error_to_js(err)),
372 }
373 }
374
375 #[cfg(feature = "odps-validation")]
385 #[wasm_bindgen]
386 pub fn validate_odps(yaml_content: &str) -> Result<(), JsValue> {
387 #[cfg(feature = "cli")]
388 {
389 use crate::cli::validation::validate_odps_internal;
390 validate_odps_internal(yaml_content).map_err(|e| JsValue::from_str(&e.to_string()))
391 }
392 #[cfg(not(feature = "cli"))]
393 {
394 use jsonschema::Validator;
396 use serde_json::Value;
397
398 let schema_content = include_str!("../schemas/odps-json-schema-latest.json");
399 let schema: Value = serde_json::from_str(schema_content)
400 .map_err(|e| JsValue::from_str(&format!("Failed to load ODPS schema: {}", e)))?;
401
402 let validator = Validator::new(&schema)
403 .map_err(|e| JsValue::from_str(&format!("Failed to compile ODPS schema: {}", e)))?;
404
405 let data: Value = serde_yaml::from_str(yaml_content)
406 .map_err(|e| JsValue::from_str(&format!("Failed to parse YAML: {}", e)))?;
407
408 if let Err(errors) = validator.validate(&data) {
409 let error_messages: Vec<String> = errors
410 .map(|e| format!("{}: {}", e.instance_path, e))
411 .collect();
412 return Err(JsValue::from_str(&format!(
413 "ODPS validation failed:\n{}",
414 error_messages.join("\n")
415 )));
416 }
417
418 Ok(())
419 }
420 }
421
422 #[cfg(not(feature = "odps-validation"))]
423 #[wasm_bindgen]
424 pub fn validate_odps(_yaml_content: &str) -> Result<(), JsValue> {
425 Ok(())
428 }
429
430 #[wasm_bindgen]
440 pub fn create_domain(name: &str) -> Result<String, JsValue> {
441 let domain = crate::models::domain::Domain::new(name.to_string());
442 serde_json::to_string(&domain)
443 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
444 }
445
446 #[wasm_bindgen]
456 pub fn import_from_domain(yaml_content: &str) -> Result<String, JsValue> {
457 match crate::models::domain::Domain::from_yaml(yaml_content) {
458 Ok(domain) => serde_json::to_string(&domain)
459 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
460 Err(e) => Err(JsValue::from_str(&format!("Parse error: {}", e))),
461 }
462 }
463
464 #[wasm_bindgen]
474 pub fn export_to_domain(domain_json: &str) -> Result<String, JsValue> {
475 let domain: crate::models::domain::Domain = serde_json::from_str(domain_json)
476 .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
477 domain
478 .to_yaml()
479 .map_err(|e| JsValue::from_str(&format!("YAML serialization error: {}", e)))
480 }
481
482 #[wasm_bindgen]
493 pub fn migrate_dataflow_to_domain(
494 dataflow_yaml: &str,
495 domain_name: Option<String>,
496 ) -> Result<String, JsValue> {
497 match crate::convert::migrate_dataflow::migrate_dataflow_to_domain(
498 dataflow_yaml,
499 domain_name.as_deref(),
500 ) {
501 Ok(domain) => serde_json::to_string(&domain)
502 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
503 Err(e) => Err(JsValue::from_str(&format!("Migration error: {}", e))),
504 }
505 }
506
507 #[wasm_bindgen]
517 pub fn parse_tag(tag_str: &str) -> Result<String, JsValue> {
518 use crate::models::Tag;
519 use std::str::FromStr;
520 match Tag::from_str(tag_str) {
521 Ok(tag) => serde_json::to_string(&tag)
522 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
523 Err(_) => Err(JsValue::from_str("Invalid tag format")),
524 }
525 }
526
527 #[wasm_bindgen]
537 pub fn serialize_tag(tag_json: &str) -> Result<String, JsValue> {
538 use crate::models::Tag;
539 let tag: Tag = serde_json::from_str(tag_json)
540 .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
541 Ok(tag.to_string())
542 }
543
544 #[wasm_bindgen]
556 pub fn convert_to_odcs(input: &str, format: Option<String>) -> Result<String, JsValue> {
557 match crate::convert::convert_to_odcs(input, format.as_deref()) {
558 Ok(yaml) => Ok(yaml),
559 Err(e) => Err(JsValue::from_str(&format!("Conversion error: {}", e))),
560 }
561 }
562
563 #[wasm_bindgen]
574 pub fn filter_nodes_by_owner(workspace_json: &str, owner: &str) -> Result<String, JsValue> {
575 let model = deserialize_workspace(workspace_json)?;
576 let filtered = model.filter_nodes_by_owner(owner);
577 serde_json::to_string(&filtered)
578 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
579 }
580
581 #[wasm_bindgen]
592 pub fn filter_relationships_by_owner(
593 workspace_json: &str,
594 owner: &str,
595 ) -> Result<String, JsValue> {
596 let model = deserialize_workspace(workspace_json)?;
597 let filtered = model.filter_relationships_by_owner(owner);
598 serde_json::to_string(&filtered)
599 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
600 }
601
602 #[wasm_bindgen]
613 pub fn filter_nodes_by_infrastructure_type(
614 workspace_json: &str,
615 infrastructure_type: &str,
616 ) -> Result<String, JsValue> {
617 let model = deserialize_workspace(workspace_json)?;
618 let infra_type: crate::models::enums::InfrastructureType =
619 serde_json::from_str(&format!("\"{}\"", infrastructure_type)).map_err(|e| {
620 JsValue::from_str(&format!(
621 "Invalid infrastructure type '{}': {}",
622 infrastructure_type, e
623 ))
624 })?;
625 let filtered = model.filter_nodes_by_infrastructure_type(infra_type);
626 serde_json::to_string(&filtered)
627 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
628 }
629
630 #[wasm_bindgen]
641 pub fn filter_relationships_by_infrastructure_type(
642 workspace_json: &str,
643 infrastructure_type: &str,
644 ) -> Result<String, JsValue> {
645 let model = deserialize_workspace(workspace_json)?;
646 let infra_type: crate::models::enums::InfrastructureType =
647 serde_json::from_str(&format!("\"{}\"", infrastructure_type)).map_err(|e| {
648 JsValue::from_str(&format!(
649 "Invalid infrastructure type '{}': {}",
650 infrastructure_type, e
651 ))
652 })?;
653 let filtered = model.filter_relationships_by_infrastructure_type(infra_type);
654 serde_json::to_string(&filtered)
655 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
656 }
657
658 #[wasm_bindgen]
669 pub fn filter_by_tags(workspace_json: &str, tag: &str) -> Result<String, JsValue> {
670 let model = deserialize_workspace(workspace_json)?;
671 let (nodes, relationships) = model.filter_by_tags(tag);
672 let result = serde_json::json!({
673 "nodes": nodes,
674 "relationships": relationships
675 });
676 serde_json::to_string(&result)
677 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
678 }
679
680 #[wasm_bindgen]
696 pub fn add_system_to_domain(
697 workspace_json: &str,
698 domain_id: &str,
699 system_json: &str,
700 ) -> Result<String, JsValue> {
701 let mut model = deserialize_workspace(workspace_json)?;
702 let domain_uuid = uuid::Uuid::parse_str(domain_id)
703 .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
704 let system: crate::models::domain::System = serde_json::from_str(system_json)
705 .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
706 model
707 .add_system_to_domain(domain_uuid, system)
708 .map_err(|e| JsValue::from_str(&e))?;
709 serde_json::to_string(&model)
710 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
711 }
712
713 #[wasm_bindgen]
725 pub fn add_cads_node_to_domain(
726 workspace_json: &str,
727 domain_id: &str,
728 node_json: &str,
729 ) -> Result<String, JsValue> {
730 let mut model = deserialize_workspace(workspace_json)?;
731 let domain_uuid = uuid::Uuid::parse_str(domain_id)
732 .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
733 let node: crate::models::domain::CADSNode = serde_json::from_str(node_json)
734 .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
735 model
736 .add_cads_node_to_domain(domain_uuid, node)
737 .map_err(|e| JsValue::from_str(&e))?;
738 serde_json::to_string(&model)
739 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
740 }
741
742 #[wasm_bindgen]
754 pub fn add_odcs_node_to_domain(
755 workspace_json: &str,
756 domain_id: &str,
757 node_json: &str,
758 ) -> Result<String, JsValue> {
759 let mut model = deserialize_workspace(workspace_json)?;
760 let domain_uuid = uuid::Uuid::parse_str(domain_id)
761 .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
762 let node: crate::models::domain::ODCSNode = serde_json::from_str(node_json)
763 .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
764 model
765 .add_odcs_node_to_domain(domain_uuid, node)
766 .map_err(|e| JsValue::from_str(&e))?;
767 serde_json::to_string(&model)
768 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
769 }
770
771 #[wasm_bindgen]
785 pub fn validate_table_name(name: &str) -> Result<String, JsValue> {
786 match crate::validation::input::validate_table_name(name) {
787 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
788 Err(err) => {
789 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
790 }
791 }
792 }
793
794 #[wasm_bindgen]
804 pub fn validate_column_name(name: &str) -> Result<String, JsValue> {
805 match crate::validation::input::validate_column_name(name) {
806 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
807 Err(err) => {
808 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
809 }
810 }
811 }
812
813 #[wasm_bindgen]
823 pub fn validate_uuid(id: &str) -> Result<String, JsValue> {
824 match crate::validation::input::validate_uuid(id) {
825 Ok(uuid) => {
826 Ok(serde_json::json!({"valid": true, "uuid": uuid.to_string()}).to_string())
827 }
828 Err(err) => {
829 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
830 }
831 }
832 }
833
834 #[wasm_bindgen]
844 pub fn validate_data_type(data_type: &str) -> Result<String, JsValue> {
845 match crate::validation::input::validate_data_type(data_type) {
846 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
847 Err(err) => {
848 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
849 }
850 }
851 }
852
853 #[wasm_bindgen]
863 pub fn validate_description(desc: &str) -> Result<String, JsValue> {
864 match crate::validation::input::validate_description(desc) {
865 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
866 Err(err) => {
867 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
868 }
869 }
870 }
871
872 #[wasm_bindgen]
883 pub fn sanitize_sql_identifier(name: &str, dialect: &str) -> String {
884 crate::validation::input::sanitize_sql_identifier(name, dialect)
885 }
886
887 #[wasm_bindgen]
897 pub fn sanitize_description(desc: &str) -> String {
898 crate::validation::input::sanitize_description(desc)
899 }
900
901 #[wasm_bindgen]
912 pub fn detect_naming_conflicts(
913 existing_tables_json: &str,
914 new_tables_json: &str,
915 ) -> Result<String, JsValue> {
916 let existing_tables: Vec<crate::models::Table> = serde_json::from_str(existing_tables_json)
917 .map_err(|e| JsValue::from_str(&format!("Failed to parse existing tables: {}", e)))?;
918 let new_tables: Vec<crate::models::Table> = serde_json::from_str(new_tables_json)
919 .map_err(|e| JsValue::from_str(&format!("Failed to parse new tables: {}", e)))?;
920
921 let validator = crate::validation::tables::TableValidator::new();
922 let conflicts = validator.detect_naming_conflicts(&existing_tables, &new_tables);
923
924 serde_json::to_string(&conflicts)
925 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
926 }
927
928 #[wasm_bindgen]
938 pub fn validate_pattern_exclusivity(table_json: &str) -> Result<String, JsValue> {
939 let table: crate::models::Table = serde_json::from_str(table_json)
940 .map_err(|e| JsValue::from_str(&format!("Failed to parse table: {}", e)))?;
941
942 let validator = crate::validation::tables::TableValidator::new();
943 match validator.validate_pattern_exclusivity(&table) {
944 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
945 Err(violation) => {
946 Ok(serde_json::json!({"valid": false, "violation": violation}).to_string())
947 }
948 }
949 }
950
951 #[wasm_bindgen]
963 pub fn check_circular_dependency(
964 relationships_json: &str,
965 source_table_id: &str,
966 target_table_id: &str,
967 ) -> Result<String, JsValue> {
968 let relationships: Vec<crate::models::Relationship> =
969 serde_json::from_str(relationships_json)
970 .map_err(|e| JsValue::from_str(&format!("Failed to parse relationships: {}", e)))?;
971
972 let source_id = uuid::Uuid::parse_str(source_table_id)
973 .map_err(|e| JsValue::from_str(&format!("Invalid source_table_id: {}", e)))?;
974 let target_id = uuid::Uuid::parse_str(target_table_id)
975 .map_err(|e| JsValue::from_str(&format!("Invalid target_table_id: {}", e)))?;
976
977 let validator = crate::validation::relationships::RelationshipValidator::new();
978 match validator.check_circular_dependency(&relationships, source_id, target_id) {
979 Ok((has_cycle, cycle_path)) => {
980 let cycle_path_strs: Vec<String> = cycle_path
981 .map(|path| path.iter().map(|id| id.to_string()).collect())
982 .unwrap_or_default();
983 Ok(serde_json::json!({
984 "has_cycle": has_cycle,
985 "cycle_path": cycle_path_strs
986 })
987 .to_string())
988 }
989 Err(err) => Err(JsValue::from_str(&format!("Validation error: {}", err))),
990 }
991 }
992
993 #[wasm_bindgen]
1004 pub fn validate_no_self_reference(
1005 source_table_id: &str,
1006 target_table_id: &str,
1007 ) -> Result<String, JsValue> {
1008 let source_id = uuid::Uuid::parse_str(source_table_id)
1009 .map_err(|e| JsValue::from_str(&format!("Invalid source_table_id: {}", e)))?;
1010 let target_id = uuid::Uuid::parse_str(target_table_id)
1011 .map_err(|e| JsValue::from_str(&format!("Invalid target_table_id: {}", e)))?;
1012
1013 let validator = crate::validation::relationships::RelationshipValidator::new();
1014 match validator.validate_no_self_reference(source_id, target_id) {
1015 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
1016 Err(self_ref) => {
1017 Ok(serde_json::json!({"valid": false, "self_reference": self_ref}).to_string())
1018 }
1019 }
1020 }
1021
1022 #[cfg(feature = "png-export")]
1038 #[wasm_bindgen]
1039 pub fn export_to_png(workspace_json: &str, width: u32, height: u32) -> Result<String, JsValue> {
1040 let model = deserialize_workspace(workspace_json)?;
1041 let exporter = crate::export::PNGExporter::new();
1042 match exporter.export(&model.tables, width, height) {
1043 Ok(result) => Ok(result.content), Err(err) => Err(export_error_to_js(err)),
1045 }
1046 }
1047
1048 #[wasm_bindgen]
1064 pub fn load_model(db_name: &str, store_name: &str, workspace_path: &str) -> js_sys::Promise {
1065 let db_name = db_name.to_string();
1066 let store_name = store_name.to_string();
1067 let workspace_path = workspace_path.to_string();
1068
1069 wasm_bindgen_futures::future_to_promise(async move {
1070 let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
1071 let loader = crate::model::ModelLoader::new(storage);
1072 match loader.load_model(&workspace_path).await {
1073 Ok(result) => serde_json::to_string(&result)
1074 .map(|s| JsValue::from_str(&s))
1075 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1076 Err(err) => Err(JsValue::from_str(&format!("Storage error: {}", err))),
1077 }
1078 })
1079 }
1080
1081 #[wasm_bindgen]
1094 pub fn save_model(
1095 db_name: &str,
1096 store_name: &str,
1097 workspace_path: &str,
1098 model_json: &str,
1099 ) -> js_sys::Promise {
1100 let db_name = db_name.to_string();
1101 let store_name = store_name.to_string();
1102 let workspace_path = workspace_path.to_string();
1103 let model_json = model_json.to_string();
1104
1105 wasm_bindgen_futures::future_to_promise(async move {
1106 let model: crate::models::DataModel = serde_json::from_str(&model_json)
1107 .map_err(|e| JsValue::from_str(&format!("Failed to parse model: {}", e)))?;
1108
1109 let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
1110 let saver = crate::model::ModelSaver::new(storage);
1111
1112 for table in &model.tables {
1115 let yaml = crate::export::ODCSExporter::export_table(table, "odcs_v3_1_0");
1117 let table_data = crate::model::saver::TableData {
1118 id: table.id,
1119 name: table.name.clone(),
1120 yaml_file_path: Some(format!("tables/{}.yaml", table.name)),
1121 yaml_value: serde_yaml::from_str(&yaml)
1122 .map_err(|e| JsValue::from_str(&format!("Failed to parse YAML: {}", e)))?,
1123 };
1124 saver
1125 .save_table(&workspace_path, &table_data)
1126 .await
1127 .map_err(|e| JsValue::from_str(&format!("Failed to save table: {}", e)))?;
1128 }
1129
1130 if !model.relationships.is_empty() {
1132 let rel_data: Vec<crate::model::saver::RelationshipData> = model
1133 .relationships
1134 .iter()
1135 .map(|rel| {
1136 let yaml_value = serde_json::json!({
1137 "id": rel.id.to_string(),
1138 "source_table_id": rel.source_table_id.to_string(),
1139 "target_table_id": rel.target_table_id.to_string(),
1140 });
1141 let yaml_str = serde_json::to_string(&yaml_value)
1143 .map_err(|e| format!("Failed to serialize relationship: {}", e))?;
1144 let yaml_value = serde_yaml::from_str(&yaml_str)
1145 .map_err(|e| format!("Failed to convert to YAML: {}", e))?;
1146 Ok(crate::model::saver::RelationshipData {
1147 id: rel.id,
1148 source_table_id: rel.source_table_id,
1149 target_table_id: rel.target_table_id,
1150 yaml_value,
1151 })
1152 })
1153 .collect::<Result<Vec<_>, String>>()
1154 .map_err(|e| JsValue::from_str(&e))?;
1155
1156 saver
1157 .save_relationships(&workspace_path, &rel_data)
1158 .await
1159 .map_err(|e| {
1160 JsValue::from_str(&format!("Failed to save relationships: {}", e))
1161 })?;
1162 }
1163
1164 Ok(JsValue::from_str("Model saved successfully"))
1165 })
1166 }
1167
1168 #[cfg(feature = "bpmn")]
1181 #[wasm_bindgen]
1182 pub fn import_bpmn_model(
1183 domain_id: &str,
1184 xml_content: &str,
1185 model_name: Option<String>,
1186 ) -> Result<String, JsValue> {
1187 use crate::import::bpmn::BPMNImporter;
1188 use uuid::Uuid;
1189
1190 let domain_uuid = Uuid::parse_str(domain_id)
1191 .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
1192
1193 let mut importer = BPMNImporter::new();
1194 match importer.import(xml_content, domain_uuid, model_name.as_deref()) {
1195 Ok(model) => serde_json::to_string(&model)
1196 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1197 Err(e) => Err(JsValue::from_str(&format!("Import error: {}", e))),
1198 }
1199 }
1200
1201 #[cfg(feature = "bpmn")]
1211 #[wasm_bindgen]
1212 pub fn export_bpmn_model(xml_content: &str) -> Result<String, JsValue> {
1213 use crate::export::bpmn::BPMNExporter;
1214 let exporter = BPMNExporter::new();
1215 exporter
1216 .export(xml_content)
1217 .map_err(|e| JsValue::from_str(&format!("Export error: {}", e)))
1218 }
1219
1220 #[cfg(feature = "dmn")]
1233 #[wasm_bindgen]
1234 pub fn import_dmn_model(
1235 domain_id: &str,
1236 xml_content: &str,
1237 model_name: Option<String>,
1238 ) -> Result<String, JsValue> {
1239 use crate::import::dmn::DMNImporter;
1240 use uuid::Uuid;
1241
1242 let domain_uuid = Uuid::parse_str(domain_id)
1243 .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
1244
1245 let mut importer = DMNImporter::new();
1246 match importer.import(xml_content, domain_uuid, model_name.as_deref()) {
1247 Ok(model) => serde_json::to_string(&model)
1248 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1249 Err(e) => Err(JsValue::from_str(&format!("Import error: {}", e))),
1250 }
1251 }
1252
1253 #[cfg(feature = "dmn")]
1263 #[wasm_bindgen]
1264 pub fn export_dmn_model(xml_content: &str) -> Result<String, JsValue> {
1265 use crate::export::dmn::DMNExporter;
1266 let exporter = DMNExporter::new();
1267 exporter
1268 .export(xml_content)
1269 .map_err(|e| JsValue::from_str(&format!("Export error: {}", e)))
1270 }
1271
1272 #[cfg(feature = "openapi")]
1285 #[wasm_bindgen]
1286 pub fn import_openapi_spec(
1287 domain_id: &str,
1288 content: &str,
1289 api_name: Option<String>,
1290 ) -> Result<String, JsValue> {
1291 use crate::import::openapi::OpenAPIImporter;
1292 use uuid::Uuid;
1293
1294 let domain_uuid = Uuid::parse_str(domain_id)
1295 .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
1296
1297 let mut importer = OpenAPIImporter::new();
1298 match importer.import(content, domain_uuid, api_name.as_deref()) {
1299 Ok(model) => serde_json::to_string(&model)
1300 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1301 Err(e) => Err(JsValue::from_str(&format!("Import error: {}", e))),
1302 }
1303 }
1304
1305 #[cfg(feature = "openapi")]
1317 #[wasm_bindgen]
1318 pub fn export_openapi_spec(
1319 content: &str,
1320 source_format: &str,
1321 target_format: Option<String>,
1322 ) -> Result<String, JsValue> {
1323 use crate::export::openapi::OpenAPIExporter;
1324 use crate::models::openapi::OpenAPIFormat;
1325
1326 let source_fmt = match source_format {
1327 "yaml" | "yml" => OpenAPIFormat::Yaml,
1328 "json" => OpenAPIFormat::Json,
1329 _ => {
1330 return Err(JsValue::from_str(
1331 "Invalid source format. Use 'yaml' or 'json'",
1332 ));
1333 }
1334 };
1335
1336 let target_fmt = if let Some(tf) = target_format {
1337 match tf.as_str() {
1338 "yaml" | "yml" => Some(OpenAPIFormat::Yaml),
1339 "json" => Some(OpenAPIFormat::Json),
1340 _ => {
1341 return Err(JsValue::from_str(
1342 "Invalid target format. Use 'yaml' or 'json'",
1343 ));
1344 }
1345 }
1346 } else {
1347 None
1348 };
1349
1350 let exporter = OpenAPIExporter::new();
1351 exporter
1352 .export(content, source_fmt, target_fmt)
1353 .map_err(|e| JsValue::from_str(&format!("Export error: {}", e)))
1354 }
1355
1356 #[cfg(feature = "openapi")]
1368 #[wasm_bindgen]
1369 pub fn convert_openapi_to_odcs(
1370 openapi_content: &str,
1371 component_name: &str,
1372 table_name: Option<String>,
1373 ) -> Result<String, JsValue> {
1374 use crate::convert::openapi_to_odcs::OpenAPIToODCSConverter;
1375
1376 let converter = OpenAPIToODCSConverter::new();
1377 match converter.convert_component(openapi_content, component_name, table_name.as_deref()) {
1378 Ok(table) => serde_json::to_string(&table)
1379 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1380 Err(e) => Err(JsValue::from_str(&format!("Conversion error: {}", e))),
1381 }
1382 }
1383
1384 #[cfg(feature = "openapi")]
1395 #[wasm_bindgen]
1396 pub fn analyze_openapi_conversion(
1397 openapi_content: &str,
1398 component_name: &str,
1399 ) -> Result<String, JsValue> {
1400 use crate::convert::openapi_to_odcs::OpenAPIToODCSConverter;
1401
1402 let converter = OpenAPIToODCSConverter::new();
1403 match converter.analyze_conversion(openapi_content, component_name) {
1404 Ok(report) => serde_json::to_string(&report)
1405 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1406 Err(e) => Err(JsValue::from_str(&format!("Analysis error: {}", e))),
1407 }
1408 }
1409}