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