data_modelling_sdk/
lib.rs

1//! Data Modelling SDK - Shared library for model operations across platforms
2//!
3//! Provides unified interfaces for:
4//! - File/folder operations (via storage backends)
5//! - Model loading/saving
6//! - Import/export functionality
7//! - Validation logic
8//! - Authentication types (shared across web, desktop, mobile)
9//! - Workspace management types
10
11pub 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// Re-export commonly used types
26#[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
53// Re-export models
54pub use models::enums::*;
55pub use models::{Column, ContactDetails, DataModel, ForeignKey, Relationship, SlaProperty, Table};
56
57// Re-export auth types
58pub use auth::{
59    AuthMode, AuthState, GitHubEmail, InitiateOAuthRequest, InitiateOAuthResponse,
60    SelectEmailRequest,
61};
62
63// Re-export workspace types
64pub use workspace::{
65    CreateWorkspaceRequest, CreateWorkspaceResponse, ListProfilesResponse, LoadProfileRequest,
66    ProfileInfo, WorkspaceInfo,
67};
68
69// Re-export Git types
70#[cfg(feature = "git")]
71pub use git::{GitError, GitService, GitStatus};
72
73// WASM bindings for import/export functions
74#[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    /// Convert ImportError to JsValue for JavaScript error handling
87    fn import_error_to_js(err: ImportError) -> JsValue {
88        JsValue::from_str(&err.to_string())
89    }
90
91    /// Convert ExportError to JsValue for JavaScript error handling
92    fn export_error_to_js(err: ExportError) -> JsValue {
93        JsValue::from_str(&err.to_string())
94    }
95
96    /// Serialize ImportResult to JSON string
97    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    /// Deserialize workspace structure from JSON string
103    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    /// Parse ODCS YAML content and return a structured workspace representation.
109    ///
110    /// # Arguments
111    ///
112    /// * `yaml_content` - ODCS YAML content as a string
113    ///
114    /// # Returns
115    ///
116    /// JSON string containing ImportResult object, or JsValue error
117    #[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    /// Export a workspace structure to ODCS YAML format.
127    ///
128    /// # Arguments
129    ///
130    /// * `workspace_json` - JSON string containing workspace/data model structure
131    ///
132    /// # Returns
133    ///
134    /// ODCS YAML format string, or JsValue error
135    #[wasm_bindgen]
136    pub fn export_to_odcs_yaml(workspace_json: &str) -> Result<String, JsValue> {
137        let model = deserialize_workspace(workspace_json)?;
138
139        // Export all tables as separate YAML documents, joined with ---\n
140        let exports = crate::export::ODCSExporter::export_model(&model, None, "odcs_v3_1_0");
141
142        // Combine all YAML documents into a single multi-document string
143        let yaml_docs: Vec<String> = exports.values().cloned().collect();
144        Ok(yaml_docs.join("\n---\n"))
145    }
146
147    /// Import data model from SQL CREATE TABLE statements.
148    ///
149    /// # Arguments
150    ///
151    /// * `sql_content` - SQL CREATE TABLE statements
152    /// * `dialect` - SQL dialect ("postgresql", "mysql", "sqlserver", "databricks")
153    ///
154    /// # Returns
155    ///
156    /// JSON string containing ImportResult object, or JsValue error
157    #[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    /// Import data model from AVRO schema.
167    ///
168    /// # Arguments
169    ///
170    /// * `avro_content` - AVRO schema JSON as a string
171    ///
172    /// # Returns
173    ///
174    /// JSON string containing ImportResult object, or JsValue error
175    #[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    /// Import data model from JSON Schema definition.
185    ///
186    /// # Arguments
187    ///
188    /// * `json_schema_content` - JSON Schema definition as a string
189    ///
190    /// # Returns
191    ///
192    /// JSON string containing ImportResult object, or JsValue error
193    #[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    /// Import data model from Protobuf schema.
203    ///
204    /// # Arguments
205    ///
206    /// * `protobuf_content` - Protobuf schema text
207    ///
208    /// # Returns
209    ///
210    /// JSON string containing ImportResult object, or JsValue error
211    #[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    /// Export a data model to SQL CREATE TABLE statements.
221    ///
222    /// # Arguments
223    ///
224    /// * `workspace_json` - JSON string containing workspace/data model structure
225    /// * `dialect` - SQL dialect ("postgresql", "mysql", "sqlserver", "databricks")
226    ///
227    /// # Returns
228    ///
229    /// SQL CREATE TABLE statements, or JsValue error
230    #[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    /// Export a data model to AVRO schema.
241    ///
242    /// # Arguments
243    ///
244    /// * `workspace_json` - JSON string containing workspace/data model structure
245    ///
246    /// # Returns
247    ///
248    /// AVRO schema JSON string, or JsValue error
249    #[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    /// Export a data model to JSON Schema definition.
260    ///
261    /// # Arguments
262    ///
263    /// * `workspace_json` - JSON string containing workspace/data model structure
264    ///
265    /// # Returns
266    ///
267    /// JSON Schema definition string, or JsValue error
268    #[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    /// Export a data model to Protobuf schema.
279    ///
280    /// # Arguments
281    ///
282    /// * `workspace_json` - JSON string containing workspace/data model structure
283    ///
284    /// # Returns
285    ///
286    /// Protobuf schema text, or JsValue error
287    #[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    /// Import CADS YAML content and return a structured representation.
298    ///
299    /// # Arguments
300    ///
301    /// * `yaml_content` - CADS YAML content as a string
302    ///
303    /// # Returns
304    ///
305    /// JSON string containing CADS asset, or JsValue error
306    #[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    /// Export a CADS asset to YAML format.
317    ///
318    /// # Arguments
319    ///
320    /// * `asset_json` - JSON string containing CADS asset
321    ///
322    /// # Returns
323    ///
324    /// CADS YAML format string, or JsValue error
325    #[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    /// Import ODPS YAML content and return a structured representation.
337    ///
338    /// # Arguments
339    ///
340    /// * `yaml_content` - ODPS YAML content as a string
341    ///
342    /// # Returns
343    ///
344    /// JSON string containing ODPS data product, or JsValue error
345    #[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    /// Export an ODPS data product to YAML format.
356    ///
357    /// # Arguments
358    ///
359    /// * `product_json` - JSON string containing ODPS data product
360    ///
361    /// # Returns
362    ///
363    /// ODPS YAML format string, or JsValue error
364    #[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    /// Validate ODPS YAML content against the ODPS JSON Schema.
376    ///
377    /// # Arguments
378    ///
379    /// * `yaml_content` - ODPS YAML content as a string
380    ///
381    /// # Returns
382    ///
383    /// Empty string on success, or error message string
384    #[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            // Inline validation when CLI feature is not enabled
395            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(error) = validator.validate(&data) {
409                return Err(JsValue::from_str(&format!(
410                    "ODPS validation failed: {}",
411                    error
412                )));
413            }
414
415            Ok(())
416        }
417    }
418
419    #[cfg(not(feature = "odps-validation"))]
420    #[wasm_bindgen]
421    pub fn validate_odps(_yaml_content: &str) -> Result<(), JsValue> {
422        // Validation disabled - feature not enabled
423        // Return success to maintain backward compatibility
424        Ok(())
425    }
426
427    /// Create a new business domain.
428    ///
429    /// # Arguments
430    ///
431    /// * `name` - Domain name
432    ///
433    /// # Returns
434    ///
435    /// JSON string containing Domain, or JsValue error
436    #[wasm_bindgen]
437    pub fn create_domain(name: &str) -> Result<String, JsValue> {
438        let domain = crate::models::domain::Domain::new(name.to_string());
439        serde_json::to_string(&domain)
440            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
441    }
442
443    /// Import Domain YAML content and return a structured representation.
444    ///
445    /// # Arguments
446    ///
447    /// * `yaml_content` - Domain YAML content as a string
448    ///
449    /// # Returns
450    ///
451    /// JSON string containing Domain, or JsValue error
452    #[wasm_bindgen]
453    pub fn import_from_domain(yaml_content: &str) -> Result<String, JsValue> {
454        match crate::models::domain::Domain::from_yaml(yaml_content) {
455            Ok(domain) => serde_json::to_string(&domain)
456                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
457            Err(e) => Err(JsValue::from_str(&format!("Parse error: {}", e))),
458        }
459    }
460
461    /// Export a Domain to YAML format.
462    ///
463    /// # Arguments
464    ///
465    /// * `domain_json` - JSON string containing Domain
466    ///
467    /// # Returns
468    ///
469    /// Domain YAML format string, or JsValue error
470    #[wasm_bindgen]
471    pub fn export_to_domain(domain_json: &str) -> Result<String, JsValue> {
472        let domain: crate::models::domain::Domain = serde_json::from_str(domain_json)
473            .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
474        domain
475            .to_yaml()
476            .map_err(|e| JsValue::from_str(&format!("YAML serialization error: {}", e)))
477    }
478
479    /// Migrate DataFlow YAML to Domain schema format.
480    ///
481    /// # Arguments
482    ///
483    /// * `dataflow_yaml` - DataFlow YAML content as a string
484    /// * `domain_name` - Optional domain name (defaults to "MigratedDomain")
485    ///
486    /// # Returns
487    ///
488    /// JSON string containing Domain, or JsValue error
489    #[wasm_bindgen]
490    pub fn migrate_dataflow_to_domain(
491        dataflow_yaml: &str,
492        domain_name: Option<String>,
493    ) -> Result<String, JsValue> {
494        match crate::convert::migrate_dataflow::migrate_dataflow_to_domain(
495            dataflow_yaml,
496            domain_name.as_deref(),
497        ) {
498            Ok(domain) => serde_json::to_string(&domain)
499                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
500            Err(e) => Err(JsValue::from_str(&format!("Migration error: {}", e))),
501        }
502    }
503
504    /// Parse a tag string into a Tag enum.
505    ///
506    /// # Arguments
507    ///
508    /// * `tag_str` - Tag string (Simple, Pair, or List format)
509    ///
510    /// # Returns
511    ///
512    /// JSON string containing Tag, or JsValue error
513    #[wasm_bindgen]
514    pub fn parse_tag(tag_str: &str) -> Result<String, JsValue> {
515        use crate::models::Tag;
516        use std::str::FromStr;
517        match Tag::from_str(tag_str) {
518            Ok(tag) => serde_json::to_string(&tag)
519                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
520            Err(_) => Err(JsValue::from_str("Invalid tag format")),
521        }
522    }
523
524    /// Serialize a Tag enum to string format.
525    ///
526    /// # Arguments
527    ///
528    /// * `tag_json` - JSON string containing Tag
529    ///
530    /// # Returns
531    ///
532    /// Tag string (Simple, Pair, or List format), or JsValue error
533    #[wasm_bindgen]
534    pub fn serialize_tag(tag_json: &str) -> Result<String, JsValue> {
535        use crate::models::Tag;
536        let tag: Tag = serde_json::from_str(tag_json)
537            .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
538        Ok(tag.to_string())
539    }
540
541    /// Convert any format to ODCS v3.1.0 YAML format.
542    ///
543    /// # Arguments
544    ///
545    /// * `input` - Format-specific content as a string
546    /// * `format` - Optional format identifier. If None, attempts auto-detection.
547    ///   Supported formats: "sql", "json_schema", "avro", "protobuf", "odcl", "odcs", "cads", "odps", "domain"
548    ///
549    /// # Returns
550    ///
551    /// ODCS v3.1.0 YAML string, or JsValue error
552    #[wasm_bindgen]
553    pub fn convert_to_odcs(input: &str, format: Option<String>) -> Result<String, JsValue> {
554        match crate::convert::convert_to_odcs(input, format.as_deref()) {
555            Ok(yaml) => Ok(yaml),
556            Err(e) => Err(JsValue::from_str(&format!("Conversion error: {}", e))),
557        }
558    }
559
560    /// Filter Data Flow nodes (tables) by owner.
561    ///
562    /// # Arguments
563    ///
564    /// * `workspace_json` - JSON string containing workspace/data model structure
565    /// * `owner` - Owner name to filter by (case-sensitive exact match)
566    ///
567    /// # Returns
568    ///
569    /// JSON string containing array of matching tables, or JsValue error
570    #[wasm_bindgen]
571    pub fn filter_nodes_by_owner(workspace_json: &str, owner: &str) -> Result<String, JsValue> {
572        let model = deserialize_workspace(workspace_json)?;
573        let filtered = model.filter_nodes_by_owner(owner);
574        serde_json::to_string(&filtered)
575            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
576    }
577
578    /// Filter Data Flow relationships by owner.
579    ///
580    /// # Arguments
581    ///
582    /// * `workspace_json` - JSON string containing workspace/data model structure
583    /// * `owner` - Owner name to filter by (case-sensitive exact match)
584    ///
585    /// # Returns
586    ///
587    /// JSON string containing array of matching relationships, or JsValue error
588    #[wasm_bindgen]
589    pub fn filter_relationships_by_owner(
590        workspace_json: &str,
591        owner: &str,
592    ) -> Result<String, JsValue> {
593        let model = deserialize_workspace(workspace_json)?;
594        let filtered = model.filter_relationships_by_owner(owner);
595        serde_json::to_string(&filtered)
596            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
597    }
598
599    /// Filter Data Flow nodes (tables) by infrastructure type.
600    ///
601    /// # Arguments
602    ///
603    /// * `workspace_json` - JSON string containing workspace/data model structure
604    /// * `infrastructure_type` - Infrastructure type string (e.g., "Kafka", "PostgreSQL")
605    ///
606    /// # Returns
607    ///
608    /// JSON string containing array of matching tables, or JsValue error
609    #[wasm_bindgen]
610    pub fn filter_nodes_by_infrastructure_type(
611        workspace_json: &str,
612        infrastructure_type: &str,
613    ) -> Result<String, JsValue> {
614        let model = deserialize_workspace(workspace_json)?;
615        let infra_type: crate::models::enums::InfrastructureType =
616            serde_json::from_str(&format!("\"{}\"", infrastructure_type)).map_err(|e| {
617                JsValue::from_str(&format!(
618                    "Invalid infrastructure type '{}': {}",
619                    infrastructure_type, e
620                ))
621            })?;
622        let filtered = model.filter_nodes_by_infrastructure_type(infra_type);
623        serde_json::to_string(&filtered)
624            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
625    }
626
627    /// Filter Data Flow relationships by infrastructure type.
628    ///
629    /// # Arguments
630    ///
631    /// * `workspace_json` - JSON string containing workspace/data model structure
632    /// * `infrastructure_type` - Infrastructure type string (e.g., "Kafka", "PostgreSQL")
633    ///
634    /// # Returns
635    ///
636    /// JSON string containing array of matching relationships, or JsValue error
637    #[wasm_bindgen]
638    pub fn filter_relationships_by_infrastructure_type(
639        workspace_json: &str,
640        infrastructure_type: &str,
641    ) -> Result<String, JsValue> {
642        let model = deserialize_workspace(workspace_json)?;
643        let infra_type: crate::models::enums::InfrastructureType =
644            serde_json::from_str(&format!("\"{}\"", infrastructure_type)).map_err(|e| {
645                JsValue::from_str(&format!(
646                    "Invalid infrastructure type '{}': {}",
647                    infrastructure_type, e
648                ))
649            })?;
650        let filtered = model.filter_relationships_by_infrastructure_type(infra_type);
651        serde_json::to_string(&filtered)
652            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
653    }
654
655    /// Filter Data Flow nodes and relationships by tag.
656    ///
657    /// # Arguments
658    ///
659    /// * `workspace_json` - JSON string containing workspace/data model structure
660    /// * `tag` - Tag to filter by
661    ///
662    /// # Returns
663    ///
664    /// JSON string containing object with `nodes` and `relationships` arrays, or JsValue error
665    #[wasm_bindgen]
666    pub fn filter_by_tags(workspace_json: &str, tag: &str) -> Result<String, JsValue> {
667        let model = deserialize_workspace(workspace_json)?;
668        let (nodes, relationships) = model.filter_by_tags(tag);
669        let result = serde_json::json!({
670            "nodes": nodes,
671            "relationships": relationships
672        });
673        serde_json::to_string(&result)
674            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
675    }
676
677    // ============================================================================
678    // Domain Operations
679    // ============================================================================
680
681    /// Add a system to a domain in a DataModel.
682    ///
683    /// # Arguments
684    ///
685    /// * `workspace_json` - JSON string containing workspace/data model structure
686    /// * `domain_id` - Domain UUID as string
687    /// * `system_json` - JSON string containing System
688    ///
689    /// # Returns
690    ///
691    /// JSON string containing updated DataModel, or JsValue error
692    #[wasm_bindgen]
693    pub fn add_system_to_domain(
694        workspace_json: &str,
695        domain_id: &str,
696        system_json: &str,
697    ) -> Result<String, JsValue> {
698        let mut model = deserialize_workspace(workspace_json)?;
699        let domain_uuid = uuid::Uuid::parse_str(domain_id)
700            .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
701        let system: crate::models::domain::System = serde_json::from_str(system_json)
702            .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
703        model
704            .add_system_to_domain(domain_uuid, system)
705            .map_err(|e| JsValue::from_str(&e))?;
706        serde_json::to_string(&model)
707            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
708    }
709
710    /// Add a CADS node to a domain in a DataModel.
711    ///
712    /// # Arguments
713    ///
714    /// * `workspace_json` - JSON string containing workspace/data model structure
715    /// * `domain_id` - Domain UUID as string
716    /// * `node_json` - JSON string containing CADSNode
717    ///
718    /// # Returns
719    ///
720    /// JSON string containing updated DataModel, or JsValue error
721    #[wasm_bindgen]
722    pub fn add_cads_node_to_domain(
723        workspace_json: &str,
724        domain_id: &str,
725        node_json: &str,
726    ) -> Result<String, JsValue> {
727        let mut model = deserialize_workspace(workspace_json)?;
728        let domain_uuid = uuid::Uuid::parse_str(domain_id)
729            .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
730        let node: crate::models::domain::CADSNode = serde_json::from_str(node_json)
731            .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
732        model
733            .add_cads_node_to_domain(domain_uuid, node)
734            .map_err(|e| JsValue::from_str(&e))?;
735        serde_json::to_string(&model)
736            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
737    }
738
739    /// Add an ODCS node to a domain in a DataModel.
740    ///
741    /// # Arguments
742    ///
743    /// * `workspace_json` - JSON string containing workspace/data model structure
744    /// * `domain_id` - Domain UUID as string
745    /// * `node_json` - JSON string containing ODCSNode
746    ///
747    /// # Returns
748    ///
749    /// JSON string containing updated DataModel, or JsValue error
750    #[wasm_bindgen]
751    pub fn add_odcs_node_to_domain(
752        workspace_json: &str,
753        domain_id: &str,
754        node_json: &str,
755    ) -> Result<String, JsValue> {
756        let mut model = deserialize_workspace(workspace_json)?;
757        let domain_uuid = uuid::Uuid::parse_str(domain_id)
758            .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
759        let node: crate::models::domain::ODCSNode = serde_json::from_str(node_json)
760            .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
761        model
762            .add_odcs_node_to_domain(domain_uuid, node)
763            .map_err(|e| JsValue::from_str(&e))?;
764        serde_json::to_string(&model)
765            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
766    }
767
768    // ============================================================================
769    // Validation Functions
770    // ============================================================================
771
772    /// Validate a table name.
773    ///
774    /// # Arguments
775    ///
776    /// * `name` - Table name to validate
777    ///
778    /// # Returns
779    ///
780    /// JSON string with validation result: `{"valid": true}` or `{"valid": false, "error": "error message"}`
781    #[wasm_bindgen]
782    pub fn validate_table_name(name: &str) -> Result<String, JsValue> {
783        match crate::validation::input::validate_table_name(name) {
784            Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
785            Err(err) => {
786                Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
787            }
788        }
789    }
790
791    /// Validate a column name.
792    ///
793    /// # Arguments
794    ///
795    /// * `name` - Column name to validate
796    ///
797    /// # Returns
798    ///
799    /// JSON string with validation result: `{"valid": true}` or `{"valid": false, "error": "error message"}`
800    #[wasm_bindgen]
801    pub fn validate_column_name(name: &str) -> Result<String, JsValue> {
802        match crate::validation::input::validate_column_name(name) {
803            Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
804            Err(err) => {
805                Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
806            }
807        }
808    }
809
810    /// Validate a UUID string.
811    ///
812    /// # Arguments
813    ///
814    /// * `id` - UUID string to validate
815    ///
816    /// # Returns
817    ///
818    /// JSON string with validation result: `{"valid": true, "uuid": "..."}` or `{"valid": false, "error": "error message"}`
819    #[wasm_bindgen]
820    pub fn validate_uuid(id: &str) -> Result<String, JsValue> {
821        match crate::validation::input::validate_uuid(id) {
822            Ok(uuid) => {
823                Ok(serde_json::json!({"valid": true, "uuid": uuid.to_string()}).to_string())
824            }
825            Err(err) => {
826                Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
827            }
828        }
829    }
830
831    /// Validate a data type string.
832    ///
833    /// # Arguments
834    ///
835    /// * `data_type` - Data type string to validate
836    ///
837    /// # Returns
838    ///
839    /// JSON string with validation result: `{"valid": true}` or `{"valid": false, "error": "error message"}`
840    #[wasm_bindgen]
841    pub fn validate_data_type(data_type: &str) -> Result<String, JsValue> {
842        match crate::validation::input::validate_data_type(data_type) {
843            Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
844            Err(err) => {
845                Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
846            }
847        }
848    }
849
850    /// Validate a description string.
851    ///
852    /// # Arguments
853    ///
854    /// * `desc` - Description string to validate
855    ///
856    /// # Returns
857    ///
858    /// JSON string with validation result: `{"valid": true}` or `{"valid": false, "error": "error message"}`
859    #[wasm_bindgen]
860    pub fn validate_description(desc: &str) -> Result<String, JsValue> {
861        match crate::validation::input::validate_description(desc) {
862            Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
863            Err(err) => {
864                Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
865            }
866        }
867    }
868
869    /// Sanitize a SQL identifier by quoting it.
870    ///
871    /// # Arguments
872    ///
873    /// * `name` - SQL identifier to sanitize
874    /// * `dialect` - SQL dialect ("postgresql", "mysql", "sqlserver", etc.)
875    ///
876    /// # Returns
877    ///
878    /// Sanitized SQL identifier string
879    #[wasm_bindgen]
880    pub fn sanitize_sql_identifier(name: &str, dialect: &str) -> String {
881        crate::validation::input::sanitize_sql_identifier(name, dialect)
882    }
883
884    /// Sanitize a description string.
885    ///
886    /// # Arguments
887    ///
888    /// * `desc` - Description string to sanitize
889    ///
890    /// # Returns
891    ///
892    /// Sanitized description string
893    #[wasm_bindgen]
894    pub fn sanitize_description(desc: &str) -> String {
895        crate::validation::input::sanitize_description(desc)
896    }
897
898    /// Detect naming conflicts between existing and new tables.
899    ///
900    /// # Arguments
901    ///
902    /// * `existing_tables_json` - JSON string containing array of existing tables
903    /// * `new_tables_json` - JSON string containing array of new tables
904    ///
905    /// # Returns
906    ///
907    /// JSON string containing array of naming conflicts
908    #[wasm_bindgen]
909    pub fn detect_naming_conflicts(
910        existing_tables_json: &str,
911        new_tables_json: &str,
912    ) -> Result<String, JsValue> {
913        let existing_tables: Vec<crate::models::Table> = serde_json::from_str(existing_tables_json)
914            .map_err(|e| JsValue::from_str(&format!("Failed to parse existing tables: {}", e)))?;
915        let new_tables: Vec<crate::models::Table> = serde_json::from_str(new_tables_json)
916            .map_err(|e| JsValue::from_str(&format!("Failed to parse new tables: {}", e)))?;
917
918        let validator = crate::validation::tables::TableValidator::new();
919        let conflicts = validator.detect_naming_conflicts(&existing_tables, &new_tables);
920
921        serde_json::to_string(&conflicts)
922            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
923    }
924
925    /// Validate pattern exclusivity for a table (SCD pattern and Data Vault classification are mutually exclusive).
926    ///
927    /// # Arguments
928    ///
929    /// * `table_json` - JSON string containing table to validate
930    ///
931    /// # Returns
932    ///
933    /// JSON string with validation result: `{"valid": true}` or `{"valid": false, "violation": {...}}`
934    #[wasm_bindgen]
935    pub fn validate_pattern_exclusivity(table_json: &str) -> Result<String, JsValue> {
936        let table: crate::models::Table = serde_json::from_str(table_json)
937            .map_err(|e| JsValue::from_str(&format!("Failed to parse table: {}", e)))?;
938
939        let validator = crate::validation::tables::TableValidator::new();
940        match validator.validate_pattern_exclusivity(&table) {
941            Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
942            Err(violation) => {
943                Ok(serde_json::json!({"valid": false, "violation": violation}).to_string())
944            }
945        }
946    }
947
948    /// Check for circular dependencies in relationships.
949    ///
950    /// # Arguments
951    ///
952    /// * `relationships_json` - JSON string containing array of existing relationships
953    /// * `source_table_id` - Source table ID (UUID string) of the new relationship
954    /// * `target_table_id` - Target table ID (UUID string) of the new relationship
955    ///
956    /// # Returns
957    ///
958    /// JSON string with result: `{"has_cycle": true/false, "cycle_path": [...]}` or error
959    #[wasm_bindgen]
960    pub fn check_circular_dependency(
961        relationships_json: &str,
962        source_table_id: &str,
963        target_table_id: &str,
964    ) -> Result<String, JsValue> {
965        let relationships: Vec<crate::models::Relationship> =
966            serde_json::from_str(relationships_json)
967                .map_err(|e| JsValue::from_str(&format!("Failed to parse relationships: {}", e)))?;
968
969        let source_id = uuid::Uuid::parse_str(source_table_id)
970            .map_err(|e| JsValue::from_str(&format!("Invalid source_table_id: {}", e)))?;
971        let target_id = uuid::Uuid::parse_str(target_table_id)
972            .map_err(|e| JsValue::from_str(&format!("Invalid target_table_id: {}", e)))?;
973
974        let validator = crate::validation::relationships::RelationshipValidator::new();
975        match validator.check_circular_dependency(&relationships, source_id, target_id) {
976            Ok((has_cycle, cycle_path)) => {
977                let cycle_path_strs: Vec<String> = cycle_path
978                    .map(|path| path.iter().map(|id| id.to_string()).collect())
979                    .unwrap_or_default();
980                Ok(serde_json::json!({
981                    "has_cycle": has_cycle,
982                    "cycle_path": cycle_path_strs
983                })
984                .to_string())
985            }
986            Err(err) => Err(JsValue::from_str(&format!("Validation error: {}", err))),
987        }
988    }
989
990    /// Validate that source and target tables are different (no self-reference).
991    ///
992    /// # Arguments
993    ///
994    /// * `source_table_id` - Source table ID (UUID string)
995    /// * `target_table_id` - Target table ID (UUID string)
996    ///
997    /// # Returns
998    ///
999    /// JSON string with validation result: `{"valid": true}` or `{"valid": false, "self_reference": {...}}`
1000    #[wasm_bindgen]
1001    pub fn validate_no_self_reference(
1002        source_table_id: &str,
1003        target_table_id: &str,
1004    ) -> Result<String, JsValue> {
1005        let source_id = uuid::Uuid::parse_str(source_table_id)
1006            .map_err(|e| JsValue::from_str(&format!("Invalid source_table_id: {}", e)))?;
1007        let target_id = uuid::Uuid::parse_str(target_table_id)
1008            .map_err(|e| JsValue::from_str(&format!("Invalid target_table_id: {}", e)))?;
1009
1010        let validator = crate::validation::relationships::RelationshipValidator::new();
1011        match validator.validate_no_self_reference(source_id, target_id) {
1012            Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
1013            Err(self_ref) => {
1014                Ok(serde_json::json!({"valid": false, "self_reference": self_ref}).to_string())
1015            }
1016        }
1017    }
1018
1019    // ============================================================================
1020    // PNG Export
1021    // ============================================================================
1022
1023    /// Export a data model to PNG image format.
1024    ///
1025    /// # Arguments
1026    ///
1027    /// * `workspace_json` - JSON string containing workspace/data model structure
1028    /// * `width` - Image width in pixels
1029    /// * `height` - Image height in pixels
1030    ///
1031    /// # Returns
1032    ///
1033    /// Base64-encoded PNG image string, or JsValue error
1034    #[cfg(feature = "png-export")]
1035    #[wasm_bindgen]
1036    pub fn export_to_png(workspace_json: &str, width: u32, height: u32) -> Result<String, JsValue> {
1037        let model = deserialize_workspace(workspace_json)?;
1038        let exporter = crate::export::PNGExporter::new();
1039        match exporter.export(&model.tables, width, height) {
1040            Ok(result) => Ok(result.content), // Already base64-encoded
1041            Err(err) => Err(export_error_to_js(err)),
1042        }
1043    }
1044
1045    // ============================================================================
1046    // Model Loading/Saving (Async)
1047    // ============================================================================
1048
1049    /// Load a model from browser storage (IndexedDB/localStorage).
1050    ///
1051    /// # Arguments
1052    ///
1053    /// * `db_name` - IndexedDB database name
1054    /// * `store_name` - Object store name
1055    /// * `workspace_path` - Workspace path to load from
1056    ///
1057    /// # Returns
1058    ///
1059    /// Promise that resolves to JSON string containing ModelLoadResult, or rejects with error
1060    #[wasm_bindgen]
1061    pub fn load_model(db_name: &str, store_name: &str, workspace_path: &str) -> js_sys::Promise {
1062        let db_name = db_name.to_string();
1063        let store_name = store_name.to_string();
1064        let workspace_path = workspace_path.to_string();
1065
1066        wasm_bindgen_futures::future_to_promise(async move {
1067            let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
1068            let loader = crate::model::ModelLoader::new(storage);
1069            match loader.load_model(&workspace_path).await {
1070                Ok(result) => serde_json::to_string(&result)
1071                    .map(|s| JsValue::from_str(&s))
1072                    .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1073                Err(err) => Err(JsValue::from_str(&format!("Storage error: {}", err))),
1074            }
1075        })
1076    }
1077
1078    /// Save a model to browser storage (IndexedDB/localStorage).
1079    ///
1080    /// # Arguments
1081    ///
1082    /// * `db_name` - IndexedDB database name
1083    /// * `store_name` - Object store name
1084    /// * `workspace_path` - Workspace path to save to
1085    /// * `model_json` - JSON string containing DataModel to save
1086    ///
1087    /// # Returns
1088    ///
1089    /// Promise that resolves to success message, or rejects with error
1090    #[wasm_bindgen]
1091    pub fn save_model(
1092        db_name: &str,
1093        store_name: &str,
1094        workspace_path: &str,
1095        model_json: &str,
1096    ) -> js_sys::Promise {
1097        let db_name = db_name.to_string();
1098        let store_name = store_name.to_string();
1099        let workspace_path = workspace_path.to_string();
1100        let model_json = model_json.to_string();
1101
1102        wasm_bindgen_futures::future_to_promise(async move {
1103            let model: crate::models::DataModel = serde_json::from_str(&model_json)
1104                .map_err(|e| JsValue::from_str(&format!("Failed to parse model: {}", e)))?;
1105
1106            let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
1107            let saver = crate::model::ModelSaver::new(storage);
1108
1109            // Convert DataModel to table/relationship data for saving
1110            // For each table, save as YAML
1111            for table in &model.tables {
1112                // Export table to ODCS YAML
1113                let yaml = crate::export::ODCSExporter::export_table(table, "odcs_v3_1_0");
1114                let table_data = crate::model::saver::TableData {
1115                    id: table.id,
1116                    name: table.name.clone(),
1117                    yaml_file_path: Some(format!("tables/{}.yaml", table.name)),
1118                    yaml_value: serde_yaml::from_str(&yaml)
1119                        .map_err(|e| JsValue::from_str(&format!("Failed to parse YAML: {}", e)))?,
1120                };
1121                saver
1122                    .save_table(&workspace_path, &table_data)
1123                    .await
1124                    .map_err(|e| JsValue::from_str(&format!("Failed to save table: {}", e)))?;
1125            }
1126
1127            // Save relationships
1128            if !model.relationships.is_empty() {
1129                let rel_data: Vec<crate::model::saver::RelationshipData> = model
1130                    .relationships
1131                    .iter()
1132                    .map(|rel| {
1133                        let yaml_value = serde_json::json!({
1134                            "id": rel.id.to_string(),
1135                            "source_table_id": rel.source_table_id.to_string(),
1136                            "target_table_id": rel.target_table_id.to_string(),
1137                        });
1138                        // Convert JSON value to YAML value
1139                        let yaml_str = serde_json::to_string(&yaml_value)
1140                            .map_err(|e| format!("Failed to serialize relationship: {}", e))?;
1141                        let yaml_value = serde_yaml::from_str(&yaml_str)
1142                            .map_err(|e| format!("Failed to convert to YAML: {}", e))?;
1143                        Ok(crate::model::saver::RelationshipData {
1144                            id: rel.id,
1145                            source_table_id: rel.source_table_id,
1146                            target_table_id: rel.target_table_id,
1147                            yaml_value,
1148                        })
1149                    })
1150                    .collect::<Result<Vec<_>, String>>()
1151                    .map_err(|e| JsValue::from_str(&e))?;
1152
1153                saver
1154                    .save_relationships(&workspace_path, &rel_data)
1155                    .await
1156                    .map_err(|e| {
1157                        JsValue::from_str(&format!("Failed to save relationships: {}", e))
1158                    })?;
1159            }
1160
1161            Ok(JsValue::from_str("Model saved successfully"))
1162        })
1163    }
1164
1165    // BPMN WASM Bindings
1166    /// Import a BPMN model from XML content.
1167    ///
1168    /// # Arguments
1169    ///
1170    /// * `domain_id` - Domain UUID as string
1171    /// * `xml_content` - BPMN XML content as a string
1172    /// * `model_name` - Optional model name (extracted from XML if not provided)
1173    ///
1174    /// # Returns
1175    ///
1176    /// JSON string containing BPMNModel, or JsValue error
1177    #[cfg(feature = "bpmn")]
1178    #[wasm_bindgen]
1179    pub fn import_bpmn_model(
1180        domain_id: &str,
1181        xml_content: &str,
1182        model_name: Option<String>,
1183    ) -> Result<String, JsValue> {
1184        use crate::import::bpmn::BPMNImporter;
1185        use uuid::Uuid;
1186
1187        let domain_uuid = Uuid::parse_str(domain_id)
1188            .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
1189
1190        let mut importer = BPMNImporter::new();
1191        match importer.import(xml_content, domain_uuid, model_name.as_deref()) {
1192            Ok(model) => serde_json::to_string(&model)
1193                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1194            Err(e) => Err(JsValue::from_str(&format!("Import error: {}", e))),
1195        }
1196    }
1197
1198    /// Export a BPMN model to XML content.
1199    ///
1200    /// # Arguments
1201    ///
1202    /// * `xml_content` - BPMN XML content as a string
1203    ///
1204    /// # Returns
1205    ///
1206    /// BPMN XML content as string, or JsValue error
1207    #[cfg(feature = "bpmn")]
1208    #[wasm_bindgen]
1209    pub fn export_bpmn_model(xml_content: &str) -> Result<String, JsValue> {
1210        use crate::export::bpmn::BPMNExporter;
1211        let exporter = BPMNExporter::new();
1212        exporter
1213            .export(xml_content)
1214            .map_err(|e| JsValue::from_str(&format!("Export error: {}", e)))
1215    }
1216
1217    // DMN WASM Bindings
1218    /// Import a DMN model from XML content.
1219    ///
1220    /// # Arguments
1221    ///
1222    /// * `domain_id` - Domain UUID as string
1223    /// * `xml_content` - DMN XML content as a string
1224    /// * `model_name` - Optional model name (extracted from XML if not provided)
1225    ///
1226    /// # Returns
1227    ///
1228    /// JSON string containing DMNModel, or JsValue error
1229    #[cfg(feature = "dmn")]
1230    #[wasm_bindgen]
1231    pub fn import_dmn_model(
1232        domain_id: &str,
1233        xml_content: &str,
1234        model_name: Option<String>,
1235    ) -> Result<String, JsValue> {
1236        use crate::import::dmn::DMNImporter;
1237        use uuid::Uuid;
1238
1239        let domain_uuid = Uuid::parse_str(domain_id)
1240            .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
1241
1242        let mut importer = DMNImporter::new();
1243        match importer.import(xml_content, domain_uuid, model_name.as_deref()) {
1244            Ok(model) => serde_json::to_string(&model)
1245                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1246            Err(e) => Err(JsValue::from_str(&format!("Import error: {}", e))),
1247        }
1248    }
1249
1250    /// Export a DMN model to XML content.
1251    ///
1252    /// # Arguments
1253    ///
1254    /// * `xml_content` - DMN XML content as a string
1255    ///
1256    /// # Returns
1257    ///
1258    /// DMN XML content as string, or JsValue error
1259    #[cfg(feature = "dmn")]
1260    #[wasm_bindgen]
1261    pub fn export_dmn_model(xml_content: &str) -> Result<String, JsValue> {
1262        use crate::export::dmn::DMNExporter;
1263        let exporter = DMNExporter::new();
1264        exporter
1265            .export(xml_content)
1266            .map_err(|e| JsValue::from_str(&format!("Export error: {}", e)))
1267    }
1268
1269    // OpenAPI WASM Bindings
1270    /// Import an OpenAPI specification from YAML or JSON content.
1271    ///
1272    /// # Arguments
1273    ///
1274    /// * `domain_id` - Domain UUID as string
1275    /// * `content` - OpenAPI YAML or JSON content as a string
1276    /// * `api_name` - Optional API name (extracted from info.title if not provided)
1277    ///
1278    /// # Returns
1279    ///
1280    /// JSON string containing OpenAPIModel, or JsValue error
1281    #[cfg(feature = "openapi")]
1282    #[wasm_bindgen]
1283    pub fn import_openapi_spec(
1284        domain_id: &str,
1285        content: &str,
1286        api_name: Option<String>,
1287    ) -> Result<String, JsValue> {
1288        use crate::import::openapi::OpenAPIImporter;
1289        use uuid::Uuid;
1290
1291        let domain_uuid = Uuid::parse_str(domain_id)
1292            .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
1293
1294        let mut importer = OpenAPIImporter::new();
1295        match importer.import(content, domain_uuid, api_name.as_deref()) {
1296            Ok(model) => serde_json::to_string(&model)
1297                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1298            Err(e) => Err(JsValue::from_str(&format!("Import error: {}", e))),
1299        }
1300    }
1301
1302    /// Export an OpenAPI specification to YAML or JSON content.
1303    ///
1304    /// # Arguments
1305    ///
1306    /// * `content` - OpenAPI content as a string
1307    /// * `source_format` - Source format ("yaml" or "json")
1308    /// * `target_format` - Optional target format for conversion (None to keep original)
1309    ///
1310    /// # Returns
1311    ///
1312    /// OpenAPI content in requested format, or JsValue error
1313    #[cfg(feature = "openapi")]
1314    #[wasm_bindgen]
1315    pub fn export_openapi_spec(
1316        content: &str,
1317        source_format: &str,
1318        target_format: Option<String>,
1319    ) -> Result<String, JsValue> {
1320        use crate::export::openapi::OpenAPIExporter;
1321        use crate::models::openapi::OpenAPIFormat;
1322
1323        let source_fmt = match source_format {
1324            "yaml" | "yml" => OpenAPIFormat::Yaml,
1325            "json" => OpenAPIFormat::Json,
1326            _ => {
1327                return Err(JsValue::from_str(
1328                    "Invalid source format. Use 'yaml' or 'json'",
1329                ));
1330            }
1331        };
1332
1333        let target_fmt = if let Some(tf) = target_format {
1334            match tf.as_str() {
1335                "yaml" | "yml" => Some(OpenAPIFormat::Yaml),
1336                "json" => Some(OpenAPIFormat::Json),
1337                _ => {
1338                    return Err(JsValue::from_str(
1339                        "Invalid target format. Use 'yaml' or 'json'",
1340                    ));
1341                }
1342            }
1343        } else {
1344            None
1345        };
1346
1347        let exporter = OpenAPIExporter::new();
1348        exporter
1349            .export(content, source_fmt, target_fmt)
1350            .map_err(|e| JsValue::from_str(&format!("Export error: {}", e)))
1351    }
1352
1353    /// Convert an OpenAPI schema component to an ODCS table.
1354    ///
1355    /// # Arguments
1356    ///
1357    /// * `openapi_content` - OpenAPI YAML or JSON content as a string
1358    /// * `component_name` - Name of the schema component to convert
1359    /// * `table_name` - Optional desired ODCS table name (uses component_name if None)
1360    ///
1361    /// # Returns
1362    ///
1363    /// JSON string containing ODCS Table, or JsValue error
1364    #[cfg(feature = "openapi")]
1365    #[wasm_bindgen]
1366    pub fn convert_openapi_to_odcs(
1367        openapi_content: &str,
1368        component_name: &str,
1369        table_name: Option<String>,
1370    ) -> Result<String, JsValue> {
1371        use crate::convert::openapi_to_odcs::OpenAPIToODCSConverter;
1372
1373        let converter = OpenAPIToODCSConverter::new();
1374        match converter.convert_component(openapi_content, component_name, table_name.as_deref()) {
1375            Ok(table) => serde_json::to_string(&table)
1376                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1377            Err(e) => Err(JsValue::from_str(&format!("Conversion error: {}", e))),
1378        }
1379    }
1380
1381    /// Analyze an OpenAPI component for conversion feasibility.
1382    ///
1383    /// # Arguments
1384    ///
1385    /// * `openapi_content` - OpenAPI YAML or JSON content as a string
1386    /// * `component_name` - Name of the schema component to analyze
1387    ///
1388    /// # Returns
1389    ///
1390    /// JSON string containing ConversionReport, or JsValue error
1391    #[cfg(feature = "openapi")]
1392    #[wasm_bindgen]
1393    pub fn analyze_openapi_conversion(
1394        openapi_content: &str,
1395        component_name: &str,
1396    ) -> Result<String, JsValue> {
1397        use crate::convert::openapi_to_odcs::OpenAPIToODCSConverter;
1398
1399        let converter = OpenAPIToODCSConverter::new();
1400        match converter.analyze_conversion(openapi_content, component_name) {
1401            Ok(report) => serde_json::to_string(&report)
1402                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1403            Err(e) => Err(JsValue::from_str(&format!("Analysis error: {}", e))),
1404        }
1405    }
1406}