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    /// Create a new business domain.
376    ///
377    /// # Arguments
378    ///
379    /// * `name` - Domain name
380    ///
381    /// # Returns
382    ///
383    /// JSON string containing Domain, or JsValue error
384    #[wasm_bindgen]
385    pub fn create_domain(name: &str) -> Result<String, JsValue> {
386        let domain = crate::models::domain::Domain::new(name.to_string());
387        serde_json::to_string(&domain)
388            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
389    }
390
391    /// Import Domain YAML content and return a structured representation.
392    ///
393    /// # Arguments
394    ///
395    /// * `yaml_content` - Domain YAML content as a string
396    ///
397    /// # Returns
398    ///
399    /// JSON string containing Domain, or JsValue error
400    #[wasm_bindgen]
401    pub fn import_from_domain(yaml_content: &str) -> Result<String, JsValue> {
402        match crate::models::domain::Domain::from_yaml(yaml_content) {
403            Ok(domain) => serde_json::to_string(&domain)
404                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
405            Err(e) => Err(JsValue::from_str(&format!("Parse error: {}", e))),
406        }
407    }
408
409    /// Export a Domain to YAML format.
410    ///
411    /// # Arguments
412    ///
413    /// * `domain_json` - JSON string containing Domain
414    ///
415    /// # Returns
416    ///
417    /// Domain YAML format string, or JsValue error
418    #[wasm_bindgen]
419    pub fn export_to_domain(domain_json: &str) -> Result<String, JsValue> {
420        let domain: crate::models::domain::Domain = serde_json::from_str(domain_json)
421            .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
422        domain
423            .to_yaml()
424            .map_err(|e| JsValue::from_str(&format!("YAML serialization error: {}", e)))
425    }
426
427    /// Migrate DataFlow YAML to Domain schema format.
428    ///
429    /// # Arguments
430    ///
431    /// * `dataflow_yaml` - DataFlow YAML content as a string
432    /// * `domain_name` - Optional domain name (defaults to "MigratedDomain")
433    ///
434    /// # Returns
435    ///
436    /// JSON string containing Domain, or JsValue error
437    #[wasm_bindgen]
438    pub fn migrate_dataflow_to_domain(
439        dataflow_yaml: &str,
440        domain_name: Option<String>,
441    ) -> Result<String, JsValue> {
442        match crate::convert::migrate_dataflow::migrate_dataflow_to_domain(
443            dataflow_yaml,
444            domain_name.as_deref(),
445        ) {
446            Ok(domain) => serde_json::to_string(&domain)
447                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
448            Err(e) => Err(JsValue::from_str(&format!("Migration error: {}", e))),
449        }
450    }
451
452    /// Parse a tag string into a Tag enum.
453    ///
454    /// # Arguments
455    ///
456    /// * `tag_str` - Tag string (Simple, Pair, or List format)
457    ///
458    /// # Returns
459    ///
460    /// JSON string containing Tag, or JsValue error
461    #[wasm_bindgen]
462    pub fn parse_tag(tag_str: &str) -> Result<String, JsValue> {
463        use crate::models::Tag;
464        use std::str::FromStr;
465        match Tag::from_str(tag_str) {
466            Ok(tag) => serde_json::to_string(&tag)
467                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
468            Err(_) => Err(JsValue::from_str("Invalid tag format")),
469        }
470    }
471
472    /// Serialize a Tag enum to string format.
473    ///
474    /// # Arguments
475    ///
476    /// * `tag_json` - JSON string containing Tag
477    ///
478    /// # Returns
479    ///
480    /// Tag string (Simple, Pair, or List format), or JsValue error
481    #[wasm_bindgen]
482    pub fn serialize_tag(tag_json: &str) -> Result<String, JsValue> {
483        use crate::models::Tag;
484        let tag: Tag = serde_json::from_str(tag_json)
485            .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
486        Ok(tag.to_string())
487    }
488
489    /// Convert any format to ODCS v3.1.0 YAML format.
490    ///
491    /// # Arguments
492    ///
493    /// * `input` - Format-specific content as a string
494    /// * `format` - Optional format identifier. If None, attempts auto-detection.
495    ///   Supported formats: "sql", "json_schema", "avro", "protobuf", "odcl", "odcs", "cads", "odps", "domain"
496    ///
497    /// # Returns
498    ///
499    /// ODCS v3.1.0 YAML string, or JsValue error
500    #[wasm_bindgen]
501    pub fn convert_to_odcs(input: &str, format: Option<String>) -> Result<String, JsValue> {
502        match crate::convert::convert_to_odcs(input, format.as_deref()) {
503            Ok(yaml) => Ok(yaml),
504            Err(e) => Err(JsValue::from_str(&format!("Conversion error: {}", e))),
505        }
506    }
507
508    /// Filter Data Flow nodes (tables) by owner.
509    ///
510    /// # Arguments
511    ///
512    /// * `workspace_json` - JSON string containing workspace/data model structure
513    /// * `owner` - Owner name to filter by (case-sensitive exact match)
514    ///
515    /// # Returns
516    ///
517    /// JSON string containing array of matching tables, or JsValue error
518    #[wasm_bindgen]
519    pub fn filter_nodes_by_owner(workspace_json: &str, owner: &str) -> Result<String, JsValue> {
520        let model = deserialize_workspace(workspace_json)?;
521        let filtered = model.filter_nodes_by_owner(owner);
522        serde_json::to_string(&filtered)
523            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
524    }
525
526    /// Filter Data Flow relationships by owner.
527    ///
528    /// # Arguments
529    ///
530    /// * `workspace_json` - JSON string containing workspace/data model structure
531    /// * `owner` - Owner name to filter by (case-sensitive exact match)
532    ///
533    /// # Returns
534    ///
535    /// JSON string containing array of matching relationships, or JsValue error
536    #[wasm_bindgen]
537    pub fn filter_relationships_by_owner(
538        workspace_json: &str,
539        owner: &str,
540    ) -> Result<String, JsValue> {
541        let model = deserialize_workspace(workspace_json)?;
542        let filtered = model.filter_relationships_by_owner(owner);
543        serde_json::to_string(&filtered)
544            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
545    }
546
547    /// Filter Data Flow nodes (tables) by infrastructure type.
548    ///
549    /// # Arguments
550    ///
551    /// * `workspace_json` - JSON string containing workspace/data model structure
552    /// * `infrastructure_type` - Infrastructure type string (e.g., "Kafka", "PostgreSQL")
553    ///
554    /// # Returns
555    ///
556    /// JSON string containing array of matching tables, or JsValue error
557    #[wasm_bindgen]
558    pub fn filter_nodes_by_infrastructure_type(
559        workspace_json: &str,
560        infrastructure_type: &str,
561    ) -> Result<String, JsValue> {
562        let model = deserialize_workspace(workspace_json)?;
563        let infra_type: crate::models::enums::InfrastructureType =
564            serde_json::from_str(&format!("\"{}\"", infrastructure_type)).map_err(|e| {
565                JsValue::from_str(&format!(
566                    "Invalid infrastructure type '{}': {}",
567                    infrastructure_type, e
568                ))
569            })?;
570        let filtered = model.filter_nodes_by_infrastructure_type(infra_type);
571        serde_json::to_string(&filtered)
572            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
573    }
574
575    /// Filter Data Flow relationships by infrastructure type.
576    ///
577    /// # Arguments
578    ///
579    /// * `workspace_json` - JSON string containing workspace/data model structure
580    /// * `infrastructure_type` - Infrastructure type string (e.g., "Kafka", "PostgreSQL")
581    ///
582    /// # Returns
583    ///
584    /// JSON string containing array of matching relationships, or JsValue error
585    #[wasm_bindgen]
586    pub fn filter_relationships_by_infrastructure_type(
587        workspace_json: &str,
588        infrastructure_type: &str,
589    ) -> Result<String, JsValue> {
590        let model = deserialize_workspace(workspace_json)?;
591        let infra_type: crate::models::enums::InfrastructureType =
592            serde_json::from_str(&format!("\"{}\"", infrastructure_type)).map_err(|e| {
593                JsValue::from_str(&format!(
594                    "Invalid infrastructure type '{}': {}",
595                    infrastructure_type, e
596                ))
597            })?;
598        let filtered = model.filter_relationships_by_infrastructure_type(infra_type);
599        serde_json::to_string(&filtered)
600            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
601    }
602
603    /// Filter Data Flow nodes and relationships by tag.
604    ///
605    /// # Arguments
606    ///
607    /// * `workspace_json` - JSON string containing workspace/data model structure
608    /// * `tag` - Tag to filter by
609    ///
610    /// # Returns
611    ///
612    /// JSON string containing object with `nodes` and `relationships` arrays, or JsValue error
613    #[wasm_bindgen]
614    pub fn filter_by_tags(workspace_json: &str, tag: &str) -> Result<String, JsValue> {
615        let model = deserialize_workspace(workspace_json)?;
616        let (nodes, relationships) = model.filter_by_tags(tag);
617        let result = serde_json::json!({
618            "nodes": nodes,
619            "relationships": relationships
620        });
621        serde_json::to_string(&result)
622            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
623    }
624
625    // ============================================================================
626    // Domain Operations
627    // ============================================================================
628
629    /// Add a system to a domain in a DataModel.
630    ///
631    /// # Arguments
632    ///
633    /// * `workspace_json` - JSON string containing workspace/data model structure
634    /// * `domain_id` - Domain UUID as string
635    /// * `system_json` - JSON string containing System
636    ///
637    /// # Returns
638    ///
639    /// JSON string containing updated DataModel, or JsValue error
640    #[wasm_bindgen]
641    pub fn add_system_to_domain(
642        workspace_json: &str,
643        domain_id: &str,
644        system_json: &str,
645    ) -> Result<String, JsValue> {
646        let mut model = deserialize_workspace(workspace_json)?;
647        let domain_uuid = uuid::Uuid::parse_str(domain_id)
648            .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
649        let system: crate::models::domain::System = serde_json::from_str(system_json)
650            .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
651        model
652            .add_system_to_domain(domain_uuid, system)
653            .map_err(|e| JsValue::from_str(&e))?;
654        serde_json::to_string(&model)
655            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
656    }
657
658    /// Add a CADS node to a domain in a DataModel.
659    ///
660    /// # Arguments
661    ///
662    /// * `workspace_json` - JSON string containing workspace/data model structure
663    /// * `domain_id` - Domain UUID as string
664    /// * `node_json` - JSON string containing CADSNode
665    ///
666    /// # Returns
667    ///
668    /// JSON string containing updated DataModel, or JsValue error
669    #[wasm_bindgen]
670    pub fn add_cads_node_to_domain(
671        workspace_json: &str,
672        domain_id: &str,
673        node_json: &str,
674    ) -> Result<String, JsValue> {
675        let mut model = deserialize_workspace(workspace_json)?;
676        let domain_uuid = uuid::Uuid::parse_str(domain_id)
677            .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
678        let node: crate::models::domain::CADSNode = serde_json::from_str(node_json)
679            .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
680        model
681            .add_cads_node_to_domain(domain_uuid, node)
682            .map_err(|e| JsValue::from_str(&e))?;
683        serde_json::to_string(&model)
684            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
685    }
686
687    /// Add an ODCS node to a domain in a DataModel.
688    ///
689    /// # Arguments
690    ///
691    /// * `workspace_json` - JSON string containing workspace/data model structure
692    /// * `domain_id` - Domain UUID as string
693    /// * `node_json` - JSON string containing ODCSNode
694    ///
695    /// # Returns
696    ///
697    /// JSON string containing updated DataModel, or JsValue error
698    #[wasm_bindgen]
699    pub fn add_odcs_node_to_domain(
700        workspace_json: &str,
701        domain_id: &str,
702        node_json: &str,
703    ) -> Result<String, JsValue> {
704        let mut model = deserialize_workspace(workspace_json)?;
705        let domain_uuid = uuid::Uuid::parse_str(domain_id)
706            .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
707        let node: crate::models::domain::ODCSNode = serde_json::from_str(node_json)
708            .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
709        model
710            .add_odcs_node_to_domain(domain_uuid, node)
711            .map_err(|e| JsValue::from_str(&e))?;
712        serde_json::to_string(&model)
713            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
714    }
715
716    // ============================================================================
717    // Validation Functions
718    // ============================================================================
719
720    /// Validate a table name.
721    ///
722    /// # Arguments
723    ///
724    /// * `name` - Table name to validate
725    ///
726    /// # Returns
727    ///
728    /// JSON string with validation result: `{"valid": true}` or `{"valid": false, "error": "error message"}`
729    #[wasm_bindgen]
730    pub fn validate_table_name(name: &str) -> Result<String, JsValue> {
731        match crate::validation::input::validate_table_name(name) {
732            Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
733            Err(err) => {
734                Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
735            }
736        }
737    }
738
739    /// Validate a column name.
740    ///
741    /// # Arguments
742    ///
743    /// * `name` - Column name to validate
744    ///
745    /// # Returns
746    ///
747    /// JSON string with validation result: `{"valid": true}` or `{"valid": false, "error": "error message"}`
748    #[wasm_bindgen]
749    pub fn validate_column_name(name: &str) -> Result<String, JsValue> {
750        match crate::validation::input::validate_column_name(name) {
751            Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
752            Err(err) => {
753                Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
754            }
755        }
756    }
757
758    /// Validate a UUID string.
759    ///
760    /// # Arguments
761    ///
762    /// * `id` - UUID string to validate
763    ///
764    /// # Returns
765    ///
766    /// JSON string with validation result: `{"valid": true, "uuid": "..."}` or `{"valid": false, "error": "error message"}`
767    #[wasm_bindgen]
768    pub fn validate_uuid(id: &str) -> Result<String, JsValue> {
769        match crate::validation::input::validate_uuid(id) {
770            Ok(uuid) => {
771                Ok(serde_json::json!({"valid": true, "uuid": uuid.to_string()}).to_string())
772            }
773            Err(err) => {
774                Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
775            }
776        }
777    }
778
779    /// Validate a data type string.
780    ///
781    /// # Arguments
782    ///
783    /// * `data_type` - Data type string to validate
784    ///
785    /// # Returns
786    ///
787    /// JSON string with validation result: `{"valid": true}` or `{"valid": false, "error": "error message"}`
788    #[wasm_bindgen]
789    pub fn validate_data_type(data_type: &str) -> Result<String, JsValue> {
790        match crate::validation::input::validate_data_type(data_type) {
791            Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
792            Err(err) => {
793                Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
794            }
795        }
796    }
797
798    /// Validate a description string.
799    ///
800    /// # Arguments
801    ///
802    /// * `desc` - Description string to validate
803    ///
804    /// # Returns
805    ///
806    /// JSON string with validation result: `{"valid": true}` or `{"valid": false, "error": "error message"}`
807    #[wasm_bindgen]
808    pub fn validate_description(desc: &str) -> Result<String, JsValue> {
809        match crate::validation::input::validate_description(desc) {
810            Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
811            Err(err) => {
812                Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
813            }
814        }
815    }
816
817    /// Sanitize a SQL identifier by quoting it.
818    ///
819    /// # Arguments
820    ///
821    /// * `name` - SQL identifier to sanitize
822    /// * `dialect` - SQL dialect ("postgresql", "mysql", "sqlserver", etc.)
823    ///
824    /// # Returns
825    ///
826    /// Sanitized SQL identifier string
827    #[wasm_bindgen]
828    pub fn sanitize_sql_identifier(name: &str, dialect: &str) -> String {
829        crate::validation::input::sanitize_sql_identifier(name, dialect)
830    }
831
832    /// Sanitize a description string.
833    ///
834    /// # Arguments
835    ///
836    /// * `desc` - Description string to sanitize
837    ///
838    /// # Returns
839    ///
840    /// Sanitized description string
841    #[wasm_bindgen]
842    pub fn sanitize_description(desc: &str) -> String {
843        crate::validation::input::sanitize_description(desc)
844    }
845
846    /// Detect naming conflicts between existing and new tables.
847    ///
848    /// # Arguments
849    ///
850    /// * `existing_tables_json` - JSON string containing array of existing tables
851    /// * `new_tables_json` - JSON string containing array of new tables
852    ///
853    /// # Returns
854    ///
855    /// JSON string containing array of naming conflicts
856    #[wasm_bindgen]
857    pub fn detect_naming_conflicts(
858        existing_tables_json: &str,
859        new_tables_json: &str,
860    ) -> Result<String, JsValue> {
861        let existing_tables: Vec<crate::models::Table> = serde_json::from_str(existing_tables_json)
862            .map_err(|e| JsValue::from_str(&format!("Failed to parse existing tables: {}", e)))?;
863        let new_tables: Vec<crate::models::Table> = serde_json::from_str(new_tables_json)
864            .map_err(|e| JsValue::from_str(&format!("Failed to parse new tables: {}", e)))?;
865
866        let validator = crate::validation::tables::TableValidator::new();
867        let conflicts = validator.detect_naming_conflicts(&existing_tables, &new_tables);
868
869        serde_json::to_string(&conflicts)
870            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
871    }
872
873    /// Validate pattern exclusivity for a table (SCD pattern and Data Vault classification are mutually exclusive).
874    ///
875    /// # Arguments
876    ///
877    /// * `table_json` - JSON string containing table to validate
878    ///
879    /// # Returns
880    ///
881    /// JSON string with validation result: `{"valid": true}` or `{"valid": false, "violation": {...}}`
882    #[wasm_bindgen]
883    pub fn validate_pattern_exclusivity(table_json: &str) -> Result<String, JsValue> {
884        let table: crate::models::Table = serde_json::from_str(table_json)
885            .map_err(|e| JsValue::from_str(&format!("Failed to parse table: {}", e)))?;
886
887        let validator = crate::validation::tables::TableValidator::new();
888        match validator.validate_pattern_exclusivity(&table) {
889            Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
890            Err(violation) => {
891                Ok(serde_json::json!({"valid": false, "violation": violation}).to_string())
892            }
893        }
894    }
895
896    /// Check for circular dependencies in relationships.
897    ///
898    /// # Arguments
899    ///
900    /// * `relationships_json` - JSON string containing array of existing relationships
901    /// * `source_table_id` - Source table ID (UUID string) of the new relationship
902    /// * `target_table_id` - Target table ID (UUID string) of the new relationship
903    ///
904    /// # Returns
905    ///
906    /// JSON string with result: `{"has_cycle": true/false, "cycle_path": [...]}` or error
907    #[wasm_bindgen]
908    pub fn check_circular_dependency(
909        relationships_json: &str,
910        source_table_id: &str,
911        target_table_id: &str,
912    ) -> Result<String, JsValue> {
913        let relationships: Vec<crate::models::Relationship> =
914            serde_json::from_str(relationships_json)
915                .map_err(|e| JsValue::from_str(&format!("Failed to parse relationships: {}", e)))?;
916
917        let source_id = uuid::Uuid::parse_str(source_table_id)
918            .map_err(|e| JsValue::from_str(&format!("Invalid source_table_id: {}", e)))?;
919        let target_id = uuid::Uuid::parse_str(target_table_id)
920            .map_err(|e| JsValue::from_str(&format!("Invalid target_table_id: {}", e)))?;
921
922        let validator = crate::validation::relationships::RelationshipValidator::new();
923        match validator.check_circular_dependency(&relationships, source_id, target_id) {
924            Ok((has_cycle, cycle_path)) => {
925                let cycle_path_strs: Vec<String> = cycle_path
926                    .map(|path| path.iter().map(|id| id.to_string()).collect())
927                    .unwrap_or_default();
928                Ok(serde_json::json!({
929                    "has_cycle": has_cycle,
930                    "cycle_path": cycle_path_strs
931                })
932                .to_string())
933            }
934            Err(err) => Err(JsValue::from_str(&format!("Validation error: {}", err))),
935        }
936    }
937
938    /// Validate that source and target tables are different (no self-reference).
939    ///
940    /// # Arguments
941    ///
942    /// * `source_table_id` - Source table ID (UUID string)
943    /// * `target_table_id` - Target table ID (UUID string)
944    ///
945    /// # Returns
946    ///
947    /// JSON string with validation result: `{"valid": true}` or `{"valid": false, "self_reference": {...}}`
948    #[wasm_bindgen]
949    pub fn validate_no_self_reference(
950        source_table_id: &str,
951        target_table_id: &str,
952    ) -> Result<String, JsValue> {
953        let source_id = uuid::Uuid::parse_str(source_table_id)
954            .map_err(|e| JsValue::from_str(&format!("Invalid source_table_id: {}", e)))?;
955        let target_id = uuid::Uuid::parse_str(target_table_id)
956            .map_err(|e| JsValue::from_str(&format!("Invalid target_table_id: {}", e)))?;
957
958        let validator = crate::validation::relationships::RelationshipValidator::new();
959        match validator.validate_no_self_reference(source_id, target_id) {
960            Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
961            Err(self_ref) => {
962                Ok(serde_json::json!({"valid": false, "self_reference": self_ref}).to_string())
963            }
964        }
965    }
966
967    // ============================================================================
968    // PNG Export
969    // ============================================================================
970
971    /// Export a data model to PNG image format.
972    ///
973    /// # Arguments
974    ///
975    /// * `workspace_json` - JSON string containing workspace/data model structure
976    /// * `width` - Image width in pixels
977    /// * `height` - Image height in pixels
978    ///
979    /// # Returns
980    ///
981    /// Base64-encoded PNG image string, or JsValue error
982    #[cfg(feature = "png-export")]
983    #[wasm_bindgen]
984    pub fn export_to_png(workspace_json: &str, width: u32, height: u32) -> Result<String, JsValue> {
985        let model = deserialize_workspace(workspace_json)?;
986        let exporter = crate::export::PNGExporter::new();
987        match exporter.export(&model.tables, width, height) {
988            Ok(result) => Ok(result.content), // Already base64-encoded
989            Err(err) => Err(export_error_to_js(err)),
990        }
991    }
992
993    // ============================================================================
994    // Model Loading/Saving (Async)
995    // ============================================================================
996
997    /// Load a model from browser storage (IndexedDB/localStorage).
998    ///
999    /// # Arguments
1000    ///
1001    /// * `db_name` - IndexedDB database name
1002    /// * `store_name` - Object store name
1003    /// * `workspace_path` - Workspace path to load from
1004    ///
1005    /// # Returns
1006    ///
1007    /// Promise that resolves to JSON string containing ModelLoadResult, or rejects with error
1008    #[wasm_bindgen]
1009    pub fn load_model(db_name: &str, store_name: &str, workspace_path: &str) -> js_sys::Promise {
1010        let db_name = db_name.to_string();
1011        let store_name = store_name.to_string();
1012        let workspace_path = workspace_path.to_string();
1013
1014        wasm_bindgen_futures::future_to_promise(async move {
1015            let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
1016            let loader = crate::model::ModelLoader::new(storage);
1017            match loader.load_model(&workspace_path).await {
1018                Ok(result) => serde_json::to_string(&result)
1019                    .map(|s| JsValue::from_str(&s))
1020                    .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1021                Err(err) => Err(JsValue::from_str(&format!("Storage error: {}", err))),
1022            }
1023        })
1024    }
1025
1026    /// Save a model to browser storage (IndexedDB/localStorage).
1027    ///
1028    /// # Arguments
1029    ///
1030    /// * `db_name` - IndexedDB database name
1031    /// * `store_name` - Object store name
1032    /// * `workspace_path` - Workspace path to save to
1033    /// * `model_json` - JSON string containing DataModel to save
1034    ///
1035    /// # Returns
1036    ///
1037    /// Promise that resolves to success message, or rejects with error
1038    #[wasm_bindgen]
1039    pub fn save_model(
1040        db_name: &str,
1041        store_name: &str,
1042        workspace_path: &str,
1043        model_json: &str,
1044    ) -> js_sys::Promise {
1045        let db_name = db_name.to_string();
1046        let store_name = store_name.to_string();
1047        let workspace_path = workspace_path.to_string();
1048        let model_json = model_json.to_string();
1049
1050        wasm_bindgen_futures::future_to_promise(async move {
1051            let model: crate::models::DataModel = serde_json::from_str(&model_json)
1052                .map_err(|e| JsValue::from_str(&format!("Failed to parse model: {}", e)))?;
1053
1054            let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
1055            let saver = crate::model::ModelSaver::new(storage);
1056
1057            // Convert DataModel to table/relationship data for saving
1058            // For each table, save as YAML
1059            for table in &model.tables {
1060                // Export table to ODCS YAML
1061                let yaml = crate::export::ODCSExporter::export_table(table, "odcs_v3_1_0");
1062                let table_data = crate::model::saver::TableData {
1063                    id: table.id,
1064                    name: table.name.clone(),
1065                    yaml_file_path: Some(format!("tables/{}.yaml", table.name)),
1066                    yaml_value: serde_yaml::from_str(&yaml)
1067                        .map_err(|e| JsValue::from_str(&format!("Failed to parse YAML: {}", e)))?,
1068                };
1069                saver
1070                    .save_table(&workspace_path, &table_data)
1071                    .await
1072                    .map_err(|e| JsValue::from_str(&format!("Failed to save table: {}", e)))?;
1073            }
1074
1075            // Save relationships
1076            if !model.relationships.is_empty() {
1077                let rel_data: Vec<crate::model::saver::RelationshipData> = model
1078                    .relationships
1079                    .iter()
1080                    .map(|rel| {
1081                        let yaml_value = serde_json::json!({
1082                            "id": rel.id.to_string(),
1083                            "source_table_id": rel.source_table_id.to_string(),
1084                            "target_table_id": rel.target_table_id.to_string(),
1085                        });
1086                        // Convert JSON value to YAML value
1087                        let yaml_str = serde_json::to_string(&yaml_value)
1088                            .map_err(|e| format!("Failed to serialize relationship: {}", e))?;
1089                        let yaml_value = serde_yaml::from_str(&yaml_str)
1090                            .map_err(|e| format!("Failed to convert to YAML: {}", e))?;
1091                        Ok(crate::model::saver::RelationshipData {
1092                            id: rel.id,
1093                            source_table_id: rel.source_table_id,
1094                            target_table_id: rel.target_table_id,
1095                            yaml_value,
1096                        })
1097                    })
1098                    .collect::<Result<Vec<_>, String>>()
1099                    .map_err(|e| JsValue::from_str(&e))?;
1100
1101                saver
1102                    .save_relationships(&workspace_path, &rel_data)
1103                    .await
1104                    .map_err(|e| {
1105                        JsValue::from_str(&format!("Failed to save relationships: {}", e))
1106                    })?;
1107            }
1108
1109            Ok(JsValue::from_str("Model saved successfully"))
1110        })
1111    }
1112
1113    // BPMN WASM Bindings
1114    /// Import a BPMN model from XML content.
1115    ///
1116    /// # Arguments
1117    ///
1118    /// * `domain_id` - Domain UUID as string
1119    /// * `xml_content` - BPMN XML content as a string
1120    /// * `model_name` - Optional model name (extracted from XML if not provided)
1121    ///
1122    /// # Returns
1123    ///
1124    /// JSON string containing BPMNModel, or JsValue error
1125    #[cfg(feature = "bpmn")]
1126    #[wasm_bindgen]
1127    pub fn import_bpmn_model(
1128        domain_id: &str,
1129        xml_content: &str,
1130        model_name: Option<String>,
1131    ) -> Result<String, JsValue> {
1132        use crate::import::bpmn::BPMNImporter;
1133        use uuid::Uuid;
1134
1135        let domain_uuid = Uuid::parse_str(domain_id)
1136            .map_err(|e| JsValue::from_str(&format!("Invalid domain ID: {}", e)))?;
1137
1138        let mut importer = BPMNImporter::new();
1139        match importer.import(xml_content, domain_uuid, model_name.as_deref()) {
1140            Ok(model) => serde_json::to_string(&model)
1141                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1142            Err(e) => Err(JsValue::from_str(&format!("Import error: {}", e))),
1143        }
1144    }
1145
1146    /// Export a BPMN model to XML content.
1147    ///
1148    /// # Arguments
1149    ///
1150    /// * `xml_content` - BPMN XML content as a string
1151    ///
1152    /// # Returns
1153    ///
1154    /// BPMN XML content as string, or JsValue error
1155    #[cfg(feature = "bpmn")]
1156    #[wasm_bindgen]
1157    pub fn export_bpmn_model(xml_content: &str) -> Result<String, JsValue> {
1158        use crate::export::bpmn::BPMNExporter;
1159        let exporter = BPMNExporter::new();
1160        exporter
1161            .export(xml_content)
1162            .map_err(|e| JsValue::from_str(&format!("Export error: {}", e)))
1163    }
1164
1165    // DMN WASM Bindings
1166    /// Import a DMN model from XML content.
1167    ///
1168    /// # Arguments
1169    ///
1170    /// * `domain_id` - Domain UUID as string
1171    /// * `xml_content` - DMN 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 DMNModel, or JsValue error
1177    #[cfg(feature = "dmn")]
1178    #[wasm_bindgen]
1179    pub fn import_dmn_model(
1180        domain_id: &str,
1181        xml_content: &str,
1182        model_name: Option<String>,
1183    ) -> Result<String, JsValue> {
1184        use crate::import::dmn::DMNImporter;
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 = DMNImporter::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 DMN model to XML content.
1199    ///
1200    /// # Arguments
1201    ///
1202    /// * `xml_content` - DMN XML content as a string
1203    ///
1204    /// # Returns
1205    ///
1206    /// DMN XML content as string, or JsValue error
1207    #[cfg(feature = "dmn")]
1208    #[wasm_bindgen]
1209    pub fn export_dmn_model(xml_content: &str) -> Result<String, JsValue> {
1210        use crate::export::dmn::DMNExporter;
1211        let exporter = DMNExporter::new();
1212        exporter
1213            .export(xml_content)
1214            .map_err(|e| JsValue::from_str(&format!("Export error: {}", e)))
1215    }
1216
1217    // OpenAPI WASM Bindings
1218    /// Import an OpenAPI specification from YAML or JSON content.
1219    ///
1220    /// # Arguments
1221    ///
1222    /// * `domain_id` - Domain UUID as string
1223    /// * `content` - OpenAPI YAML or JSON content as a string
1224    /// * `api_name` - Optional API name (extracted from info.title if not provided)
1225    ///
1226    /// # Returns
1227    ///
1228    /// JSON string containing OpenAPIModel, or JsValue error
1229    #[cfg(feature = "openapi")]
1230    #[wasm_bindgen]
1231    pub fn import_openapi_spec(
1232        domain_id: &str,
1233        content: &str,
1234        api_name: Option<String>,
1235    ) -> Result<String, JsValue> {
1236        use crate::import::openapi::OpenAPIImporter;
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 = OpenAPIImporter::new();
1243        match importer.import(content, domain_uuid, api_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 an OpenAPI specification to YAML or JSON content.
1251    ///
1252    /// # Arguments
1253    ///
1254    /// * `content` - OpenAPI content as a string
1255    /// * `source_format` - Source format ("yaml" or "json")
1256    /// * `target_format` - Optional target format for conversion (None to keep original)
1257    ///
1258    /// # Returns
1259    ///
1260    /// OpenAPI content in requested format, or JsValue error
1261    #[cfg(feature = "openapi")]
1262    #[wasm_bindgen]
1263    pub fn export_openapi_spec(
1264        content: &str,
1265        source_format: &str,
1266        target_format: Option<String>,
1267    ) -> Result<String, JsValue> {
1268        use crate::export::openapi::OpenAPIExporter;
1269        use crate::models::openapi::OpenAPIFormat;
1270
1271        let source_fmt = match source_format {
1272            "yaml" | "yml" => OpenAPIFormat::Yaml,
1273            "json" => OpenAPIFormat::Json,
1274            _ => {
1275                return Err(JsValue::from_str(
1276                    "Invalid source format. Use 'yaml' or 'json'",
1277                ));
1278            }
1279        };
1280
1281        let target_fmt = if let Some(tf) = target_format {
1282            match tf.as_str() {
1283                "yaml" | "yml" => Some(OpenAPIFormat::Yaml),
1284                "json" => Some(OpenAPIFormat::Json),
1285                _ => {
1286                    return Err(JsValue::from_str(
1287                        "Invalid target format. Use 'yaml' or 'json'",
1288                    ));
1289                }
1290            }
1291        } else {
1292            None
1293        };
1294
1295        let exporter = OpenAPIExporter::new();
1296        exporter
1297            .export(content, source_fmt, target_fmt)
1298            .map_err(|e| JsValue::from_str(&format!("Export error: {}", e)))
1299    }
1300
1301    /// Convert an OpenAPI schema component to an ODCS table.
1302    ///
1303    /// # Arguments
1304    ///
1305    /// * `openapi_content` - OpenAPI YAML or JSON content as a string
1306    /// * `component_name` - Name of the schema component to convert
1307    /// * `table_name` - Optional desired ODCS table name (uses component_name if None)
1308    ///
1309    /// # Returns
1310    ///
1311    /// JSON string containing ODCS Table, or JsValue error
1312    #[cfg(feature = "openapi")]
1313    #[wasm_bindgen]
1314    pub fn convert_openapi_to_odcs(
1315        openapi_content: &str,
1316        component_name: &str,
1317        table_name: Option<String>,
1318    ) -> Result<String, JsValue> {
1319        use crate::convert::openapi_to_odcs::OpenAPIToODCSConverter;
1320
1321        let converter = OpenAPIToODCSConverter::new();
1322        match converter.convert_component(openapi_content, component_name, table_name.as_deref()) {
1323            Ok(table) => serde_json::to_string(&table)
1324                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1325            Err(e) => Err(JsValue::from_str(&format!("Conversion error: {}", e))),
1326        }
1327    }
1328
1329    /// Analyze an OpenAPI component for conversion feasibility.
1330    ///
1331    /// # Arguments
1332    ///
1333    /// * `openapi_content` - OpenAPI YAML or JSON content as a string
1334    /// * `component_name` - Name of the schema component to analyze
1335    ///
1336    /// # Returns
1337    ///
1338    /// JSON string containing ConversionReport, or JsValue error
1339    #[cfg(feature = "openapi")]
1340    #[wasm_bindgen]
1341    pub fn analyze_openapi_conversion(
1342        openapi_content: &str,
1343        component_name: &str,
1344    ) -> Result<String, JsValue> {
1345        use crate::convert::openapi_to_odcs::OpenAPIToODCSConverter;
1346
1347        let converter = OpenAPIToODCSConverter::new();
1348        match converter.analyze_conversion(openapi_content, component_name) {
1349            Ok(report) => serde_json::to_string(&report)
1350                .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
1351            Err(e) => Err(JsValue::from_str(&format!("Analysis error: {}", e))),
1352        }
1353    }
1354}