data_modelling_sdk/
lib.rs

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