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,
36};
37pub use import::{
38 AvroImporter, ImportError, ImportResult, JSONSchemaImporter, ODCSImporter, ProtobufImporter,
39 SQLImporter,
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, DataModel, ForeignKey, Relationship, 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]
307 pub fn validate_table_name(name: &str) -> Result<String, JsValue> {
308 match crate::validation::input::validate_table_name(name) {
309 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
310 Err(err) => {
311 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
312 }
313 }
314 }
315
316 #[wasm_bindgen]
326 pub fn validate_column_name(name: &str) -> Result<String, JsValue> {
327 match crate::validation::input::validate_column_name(name) {
328 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
329 Err(err) => {
330 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
331 }
332 }
333 }
334
335 #[wasm_bindgen]
345 pub fn validate_uuid(id: &str) -> Result<String, JsValue> {
346 match crate::validation::input::validate_uuid(id) {
347 Ok(uuid) => {
348 Ok(serde_json::json!({"valid": true, "uuid": uuid.to_string()}).to_string())
349 }
350 Err(err) => {
351 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
352 }
353 }
354 }
355
356 #[wasm_bindgen]
366 pub fn validate_data_type(data_type: &str) -> Result<String, JsValue> {
367 match crate::validation::input::validate_data_type(data_type) {
368 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
369 Err(err) => {
370 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
371 }
372 }
373 }
374
375 #[wasm_bindgen]
385 pub fn validate_description(desc: &str) -> Result<String, JsValue> {
386 match crate::validation::input::validate_description(desc) {
387 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
388 Err(err) => {
389 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
390 }
391 }
392 }
393
394 #[wasm_bindgen]
405 pub fn sanitize_sql_identifier(name: &str, dialect: &str) -> String {
406 crate::validation::input::sanitize_sql_identifier(name, dialect)
407 }
408
409 #[wasm_bindgen]
419 pub fn sanitize_description(desc: &str) -> String {
420 crate::validation::input::sanitize_description(desc)
421 }
422
423 #[wasm_bindgen]
434 pub fn detect_naming_conflicts(
435 existing_tables_json: &str,
436 new_tables_json: &str,
437 ) -> Result<String, JsValue> {
438 let existing_tables: Vec<crate::models::Table> = serde_json::from_str(existing_tables_json)
439 .map_err(|e| JsValue::from_str(&format!("Failed to parse existing tables: {}", e)))?;
440 let new_tables: Vec<crate::models::Table> = serde_json::from_str(new_tables_json)
441 .map_err(|e| JsValue::from_str(&format!("Failed to parse new tables: {}", e)))?;
442
443 let validator = crate::validation::tables::TableValidator::new();
444 let conflicts = validator.detect_naming_conflicts(&existing_tables, &new_tables);
445
446 serde_json::to_string(&conflicts)
447 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
448 }
449
450 #[wasm_bindgen]
460 pub fn validate_pattern_exclusivity(table_json: &str) -> Result<String, JsValue> {
461 let table: crate::models::Table = serde_json::from_str(table_json)
462 .map_err(|e| JsValue::from_str(&format!("Failed to parse table: {}", e)))?;
463
464 let validator = crate::validation::tables::TableValidator::new();
465 match validator.validate_pattern_exclusivity(&table) {
466 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
467 Err(violation) => {
468 Ok(serde_json::json!({"valid": false, "violation": violation}).to_string())
469 }
470 }
471 }
472
473 #[wasm_bindgen]
485 pub fn check_circular_dependency(
486 relationships_json: &str,
487 source_table_id: &str,
488 target_table_id: &str,
489 ) -> Result<String, JsValue> {
490 let relationships: Vec<crate::models::Relationship> =
491 serde_json::from_str(relationships_json)
492 .map_err(|e| JsValue::from_str(&format!("Failed to parse relationships: {}", e)))?;
493
494 let source_id = uuid::Uuid::parse_str(source_table_id)
495 .map_err(|e| JsValue::from_str(&format!("Invalid source_table_id: {}", e)))?;
496 let target_id = uuid::Uuid::parse_str(target_table_id)
497 .map_err(|e| JsValue::from_str(&format!("Invalid target_table_id: {}", e)))?;
498
499 let validator = crate::validation::relationships::RelationshipValidator::new();
500 match validator.check_circular_dependency(&relationships, source_id, target_id) {
501 Ok((has_cycle, cycle_path)) => {
502 let cycle_path_strs: Vec<String> = cycle_path
503 .map(|path| path.iter().map(|id| id.to_string()).collect())
504 .unwrap_or_default();
505 Ok(serde_json::json!({
506 "has_cycle": has_cycle,
507 "cycle_path": cycle_path_strs
508 })
509 .to_string())
510 }
511 Err(err) => Err(JsValue::from_str(&format!("Validation error: {}", err))),
512 }
513 }
514
515 #[wasm_bindgen]
526 pub fn validate_no_self_reference(
527 source_table_id: &str,
528 target_table_id: &str,
529 ) -> Result<String, JsValue> {
530 let source_id = uuid::Uuid::parse_str(source_table_id)
531 .map_err(|e| JsValue::from_str(&format!("Invalid source_table_id: {}", e)))?;
532 let target_id = uuid::Uuid::parse_str(target_table_id)
533 .map_err(|e| JsValue::from_str(&format!("Invalid target_table_id: {}", e)))?;
534
535 let validator = crate::validation::relationships::RelationshipValidator::new();
536 match validator.validate_no_self_reference(source_id, target_id) {
537 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
538 Err(self_ref) => {
539 Ok(serde_json::json!({"valid": false, "self_reference": self_ref}).to_string())
540 }
541 }
542 }
543
544 #[cfg(feature = "png-export")]
560 #[wasm_bindgen]
561 pub fn export_to_png(workspace_json: &str, width: u32, height: u32) -> Result<String, JsValue> {
562 let model = deserialize_workspace(workspace_json)?;
563 let exporter = crate::export::PNGExporter::new();
564 match exporter.export(&model.tables, width, height) {
565 Ok(result) => Ok(result.content), Err(err) => Err(export_error_to_js(err)),
567 }
568 }
569
570 #[wasm_bindgen]
586 pub fn load_model(db_name: &str, store_name: &str, workspace_path: &str) -> js_sys::Promise {
587 let db_name = db_name.to_string();
588 let store_name = store_name.to_string();
589 let workspace_path = workspace_path.to_string();
590
591 wasm_bindgen_futures::future_to_promise(async move {
592 let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
593 let loader = crate::model::ModelLoader::new(storage);
594 match loader.load_model(&workspace_path).await {
595 Ok(result) => serde_json::to_string(&result)
596 .map(|s| JsValue::from_str(&s))
597 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
598 Err(err) => Err(JsValue::from_str(&format!("Storage error: {}", err))),
599 }
600 })
601 }
602
603 #[wasm_bindgen]
616 pub fn save_model(
617 db_name: &str,
618 store_name: &str,
619 workspace_path: &str,
620 model_json: &str,
621 ) -> js_sys::Promise {
622 let db_name = db_name.to_string();
623 let store_name = store_name.to_string();
624 let workspace_path = workspace_path.to_string();
625 let model_json = model_json.to_string();
626
627 wasm_bindgen_futures::future_to_promise(async move {
628 let model: crate::models::DataModel = serde_json::from_str(&model_json)
629 .map_err(|e| JsValue::from_str(&format!("Failed to parse model: {}", e)))?;
630
631 let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
632 let saver = crate::model::ModelSaver::new(storage);
633
634 for table in &model.tables {
637 let yaml = crate::export::ODCSExporter::export_table(table, "odcs_v3_1_0");
639 let table_data = crate::model::saver::TableData {
640 id: table.id,
641 name: table.name.clone(),
642 yaml_file_path: Some(format!("tables/{}.yaml", table.name)),
643 yaml_value: serde_yaml::from_str(&yaml)
644 .map_err(|e| JsValue::from_str(&format!("Failed to parse YAML: {}", e)))?,
645 };
646 saver
647 .save_table(&workspace_path, &table_data)
648 .await
649 .map_err(|e| JsValue::from_str(&format!("Failed to save table: {}", e)))?;
650 }
651
652 if !model.relationships.is_empty() {
654 let rel_data: Vec<crate::model::saver::RelationshipData> = model
655 .relationships
656 .iter()
657 .map(|rel| {
658 let yaml_value = serde_json::json!({
659 "id": rel.id.to_string(),
660 "source_table_id": rel.source_table_id.to_string(),
661 "target_table_id": rel.target_table_id.to_string(),
662 });
663 let yaml_str = serde_json::to_string(&yaml_value)
665 .map_err(|e| format!("Failed to serialize relationship: {}", e))?;
666 let yaml_value = serde_yaml::from_str(&yaml_str)
667 .map_err(|e| format!("Failed to convert to YAML: {}", e))?;
668 Ok(crate::model::saver::RelationshipData {
669 id: rel.id,
670 source_table_id: rel.source_table_id,
671 target_table_id: rel.target_table_id,
672 yaml_value,
673 })
674 })
675 .collect::<Result<Vec<_>, String>>()
676 .map_err(|e| JsValue::from_str(&e))?;
677
678 saver
679 .save_relationships(&workspace_path, &rel_data)
680 .await
681 .map_err(|e| {
682 JsValue::from_str(&format!("Failed to save relationships: {}", e))
683 })?;
684 }
685
686 Ok(JsValue::from_str("Model saved successfully"))
687 })
688 }
689}