1pub mod auth;
12#[cfg(feature = "cli")]
13pub mod cli;
14pub mod convert;
15#[cfg(feature = "database")]
16pub mod database;
17pub mod export;
18#[cfg(feature = "git")]
19pub mod git;
20pub mod import;
21pub mod model;
22pub mod models;
23pub mod storage;
24pub mod validation;
25pub mod workspace;
26
27#[cfg(feature = "api-backend")]
29pub use storage::api::ApiStorageBackend;
30#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
31pub use storage::browser::BrowserStorageBackend;
32#[cfg(feature = "native-fs")]
33pub use storage::filesystem::FileSystemStorageBackend;
34pub use storage::{StorageBackend, StorageError};
35
36pub use convert::{ConversionError, convert_to_odcs};
37#[cfg(feature = "png-export")]
38pub use export::PNGExporter;
39pub use export::{
40 AvroExporter, ExportError, ExportResult, JSONSchemaExporter, ODCSExporter, ProtobufExporter,
41 SQLExporter,
42};
43pub use import::{
44 AvroImporter, ImportError, ImportResult, JSONSchemaImporter, ODCSImporter, ProtobufImporter,
45 SQLImporter,
46};
47#[cfg(feature = "api-backend")]
48pub use model::ApiModelLoader;
49pub use model::{ModelLoader, ModelSaver};
50pub use validation::{
51 RelationshipValidationError, RelationshipValidationResult, TableValidationError,
52 TableValidationResult,
53};
54
55pub use models::enums::*;
57pub use models::{Column, ContactDetails, DataModel, ForeignKey, Relationship, SlaProperty, Table};
58
59pub use auth::{
61 AuthMode, AuthState, GitHubEmail, InitiateOAuthRequest, InitiateOAuthResponse,
62 SelectEmailRequest,
63};
64
65pub use workspace::{
67 CreateWorkspaceRequest, CreateWorkspaceResponse, ListProfilesResponse, LoadProfileRequest,
68 ProfileInfo, WorkspaceInfo,
69};
70
71#[cfg(feature = "git")]
73pub use git::{GitError, GitService, GitStatus};
74
75#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
77mod wasm {
78 use crate::export::ExportError;
79 use crate::import::{ImportError, ImportResult};
80 use crate::models::DataModel;
81 use js_sys;
82 use serde::{Deserialize, Serialize};
83 use serde_json;
84 use serde_yaml;
85 use uuid;
86 use wasm_bindgen::prelude::*;
87 use wasm_bindgen_futures;
88
89 #[derive(Debug, Clone, Serialize, Deserialize)]
92 pub struct WasmError {
93 pub error_type: String,
95 pub message: String,
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub code: Option<String>,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub details: Option<serde_json::Value>,
103 }
104
105 impl WasmError {
106 fn new(error_type: impl Into<String>, message: impl Into<String>) -> Self {
108 Self {
109 error_type: error_type.into(),
110 message: message.into(),
111 code: None,
112 details: None,
113 }
114 }
115
116 fn with_code(mut self, code: impl Into<String>) -> Self {
118 self.code = Some(code.into());
119 self
120 }
121
122 fn to_js_value(&self) -> JsValue {
124 match serde_json::to_string(self) {
126 Ok(json) => JsValue::from_str(&json),
127 Err(_) => JsValue::from_str(&self.message),
129 }
130 }
131 }
132
133 fn import_error_to_js(err: ImportError) -> JsValue {
135 WasmError::new("ImportError", err.to_string())
136 .with_code("IMPORT_FAILED")
137 .to_js_value()
138 }
139
140 fn export_error_to_js(err: ExportError) -> JsValue {
142 WasmError::new("ExportError", err.to_string())
143 .with_code("EXPORT_FAILED")
144 .to_js_value()
145 }
146
147 fn serialization_error(err: impl std::fmt::Display) -> JsValue {
149 WasmError::new(
150 "SerializationError",
151 format!("Serialization error: {}", err),
152 )
153 .with_code("SERIALIZATION_FAILED")
154 .to_js_value()
155 }
156
157 fn deserialization_error(err: impl std::fmt::Display) -> JsValue {
159 WasmError::new(
160 "DeserializationError",
161 format!("Deserialization error: {}", err),
162 )
163 .with_code("DESERIALIZATION_FAILED")
164 .to_js_value()
165 }
166
167 fn parse_error(err: impl std::fmt::Display) -> JsValue {
169 WasmError::new("ParseError", format!("Parse error: {}", err))
170 .with_code("PARSE_FAILED")
171 .to_js_value()
172 }
173
174 fn validation_error(err: impl std::fmt::Display) -> JsValue {
176 WasmError::new("ValidationError", err.to_string())
177 .with_code("VALIDATION_FAILED")
178 .to_js_value()
179 }
180
181 fn invalid_input_error(field: &str, err: impl std::fmt::Display) -> JsValue {
183 WasmError::new("InvalidInputError", format!("Invalid {}: {}", field, err))
184 .with_code("INVALID_INPUT")
185 .to_js_value()
186 }
187
188 fn conversion_error(err: impl std::fmt::Display) -> JsValue {
190 WasmError::new("ConversionError", format!("Conversion error: {}", err))
191 .with_code("CONVERSION_FAILED")
192 .to_js_value()
193 }
194
195 fn storage_error(err: impl std::fmt::Display) -> JsValue {
197 WasmError::new("StorageError", format!("Storage error: {}", err))
198 .with_code("STORAGE_FAILED")
199 .to_js_value()
200 }
201
202 fn serialize_import_result(result: &ImportResult) -> Result<String, JsValue> {
204 serde_json::to_string(result).map_err(serialization_error)
205 }
206
207 fn flatten_struct_columns(result: ImportResult) -> ImportResult {
215 use crate::import::{ColumnData, ODCSImporter, TableData};
216
217 let importer = ODCSImporter::new();
218
219 let tables = result
220 .tables
221 .into_iter()
222 .map(|table_data| {
223 let mut all_columns = Vec::new();
224
225 for col_data in table_data.columns {
226 let data_type_upper = col_data.data_type.to_uppercase();
227 let is_map = data_type_upper.starts_with("MAP<");
228
229 if is_map {
231 all_columns.push(col_data);
232 continue;
233 }
234
235 let is_struct = data_type_upper.contains("STRUCT<");
237 if is_struct {
238 let field_data = serde_json::Map::new();
239 if let Ok(nested_cols) = importer.parse_struct_type_from_string(
240 &col_data.name,
241 &col_data.data_type,
242 &field_data,
243 ) {
244 if !nested_cols.is_empty() {
245 let parent_data_type =
247 if col_data.data_type.to_uppercase().starts_with("ARRAY<") {
248 "ARRAY<STRUCT<...>>".to_string()
249 } else {
250 "STRUCT<...>".to_string()
251 };
252
253 all_columns.push(ColumnData {
254 name: col_data.name.clone(),
255 data_type: parent_data_type,
256 physical_type: col_data.physical_type.clone(),
257 nullable: col_data.nullable,
258 primary_key: col_data.primary_key,
259 description: col_data.description.clone(),
260 quality: col_data.quality.clone(),
261 relationships: col_data.relationships.clone(),
262 enum_values: col_data.enum_values.clone(),
263 ..Default::default()
264 });
265
266 for nested_col in nested_cols {
268 all_columns.push(
269 crate::import::odcs_shared::column_to_column_data(
270 &nested_col,
271 ),
272 );
273 }
274 continue;
275 }
276 }
277 }
278
279 all_columns.push(col_data);
281 }
282
283 TableData {
284 table_index: table_data.table_index,
285 id: table_data.id.clone(),
286 name: table_data.name.clone(),
287 columns: all_columns,
288 api_version: table_data.api_version.clone(),
289 version: table_data.version.clone(),
290 status: table_data.status.clone(),
291 kind: table_data.kind.clone(),
292 domain: table_data.domain.clone(),
293 data_product: table_data.data_product.clone(),
294 tenant: table_data.tenant.clone(),
295 description: table_data.description.clone(),
296 servers: table_data.servers.clone(),
297 team: table_data.team.clone(),
298 support: table_data.support.clone(),
299 roles: table_data.roles.clone(),
300 sla_properties: table_data.sla_properties.clone(),
301 quality: table_data.quality.clone(),
302 price: table_data.price.clone(),
303 tags: table_data.tags.clone(),
304 custom_properties: table_data.custom_properties.clone(),
305 authoritative_definitions: table_data.authoritative_definitions.clone(),
306 contract_created_ts: table_data.contract_created_ts.clone(),
307 odcs_metadata: table_data.odcs_metadata.clone(),
308 }
309 })
310 .collect();
311
312 ImportResult {
313 tables,
314 tables_requiring_name: result.tables_requiring_name,
315 errors: result.errors,
316 ai_suggestions: result.ai_suggestions,
317 }
318 }
319
320 fn deserialize_workspace(json: &str) -> Result<DataModel, JsValue> {
322 serde_json::from_str(json).map_err(deserialization_error)
323 }
324
325 #[wasm_bindgen]
335 pub fn parse_odcs_yaml(yaml_content: &str) -> Result<String, JsValue> {
336 let mut importer = crate::import::ODCSImporter::new();
337 match importer.import(yaml_content) {
338 Ok(result) => {
339 let flattened = flatten_struct_columns(result);
340 serialize_import_result(&flattened)
341 }
342 Err(err) => Err(import_error_to_js(err)),
343 }
344 }
345
346 #[wasm_bindgen]
362 pub fn parse_odcl_yaml(yaml_content: &str) -> Result<String, JsValue> {
363 let mut importer = crate::import::ODCLImporter::new();
364 match importer.import(yaml_content) {
365 Ok(result) => {
366 let flattened = flatten_struct_columns(result);
367 serialize_import_result(&flattened)
368 }
369 Err(err) => Err(import_error_to_js(err)),
370 }
371 }
372
373 #[wasm_bindgen]
386 pub fn is_odcl_format(yaml_content: &str) -> bool {
387 let importer = crate::import::ODCLImporter::new();
388 importer.can_handle(yaml_content)
389 }
390
391 #[wasm_bindgen]
401 pub fn export_to_odcs_yaml(workspace_json: &str) -> Result<String, JsValue> {
402 let model = deserialize_workspace(workspace_json)?;
403
404 let exports = crate::export::ODCSExporter::export_model(&model, None, "odcs_v3_1_0");
406
407 let yaml_docs: Vec<String> = exports.values().cloned().collect();
409 Ok(yaml_docs.join("\n---\n"))
410 }
411
412 #[wasm_bindgen]
423 pub fn import_from_sql(sql_content: &str, dialect: &str) -> Result<String, JsValue> {
424 let importer = crate::import::SQLImporter::new(dialect);
425 match importer.parse(sql_content) {
426 Ok(result) => {
427 let flattened = flatten_struct_columns(result);
429 serialize_import_result(&flattened)
430 }
431 Err(err) => Err(parse_error(err)),
432 }
433 }
434
435 #[wasm_bindgen]
445 pub fn import_from_avro(avro_content: &str) -> Result<String, JsValue> {
446 let importer = crate::import::AvroImporter::new();
447 match importer.import(avro_content) {
448 Ok(result) => {
449 let flattened = flatten_struct_columns(result);
450 serialize_import_result(&flattened)
451 }
452 Err(err) => Err(import_error_to_js(err)),
453 }
454 }
455
456 #[wasm_bindgen]
466 pub fn import_from_json_schema(json_schema_content: &str) -> Result<String, JsValue> {
467 let importer = crate::import::JSONSchemaImporter::new();
468 match importer.import(json_schema_content) {
469 Ok(result) => {
470 let flattened = flatten_struct_columns(result);
471 serialize_import_result(&flattened)
472 }
473 Err(err) => Err(import_error_to_js(err)),
474 }
475 }
476
477 #[wasm_bindgen]
487 pub fn import_from_protobuf(protobuf_content: &str) -> Result<String, JsValue> {
488 let importer = crate::import::ProtobufImporter::new();
489 match importer.import(protobuf_content) {
490 Ok(result) => {
491 let flattened = flatten_struct_columns(result);
492 serialize_import_result(&flattened)
493 }
494 Err(err) => Err(import_error_to_js(err)),
495 }
496 }
497
498 #[wasm_bindgen]
509 pub fn export_to_sql(workspace_json: &str, dialect: &str) -> Result<String, JsValue> {
510 let model = deserialize_workspace(workspace_json)?;
511 let exporter = crate::export::SQLExporter;
512 match exporter.export(&model.tables, Some(dialect)) {
513 Ok(result) => Ok(result.content),
514 Err(err) => Err(export_error_to_js(err)),
515 }
516 }
517
518 #[wasm_bindgen]
528 pub fn export_to_avro(workspace_json: &str) -> Result<String, JsValue> {
529 let model = deserialize_workspace(workspace_json)?;
530 let exporter = crate::export::AvroExporter;
531 match exporter.export(&model.tables) {
532 Ok(result) => Ok(result.content),
533 Err(err) => Err(export_error_to_js(err)),
534 }
535 }
536
537 #[wasm_bindgen]
547 pub fn export_to_json_schema(workspace_json: &str) -> Result<String, JsValue> {
548 let model = deserialize_workspace(workspace_json)?;
549 let exporter = crate::export::JSONSchemaExporter;
550 match exporter.export(&model.tables) {
551 Ok(result) => Ok(result.content),
552 Err(err) => Err(export_error_to_js(err)),
553 }
554 }
555
556 #[wasm_bindgen]
566 pub fn export_to_protobuf(workspace_json: &str) -> Result<String, JsValue> {
567 let model = deserialize_workspace(workspace_json)?;
568 let exporter = crate::export::ProtobufExporter;
569 match exporter.export(&model.tables) {
570 Ok(result) => Ok(result.content),
571 Err(err) => Err(export_error_to_js(err)),
572 }
573 }
574
575 #[wasm_bindgen]
585 pub fn import_from_cads(yaml_content: &str) -> Result<String, JsValue> {
586 let importer = crate::import::CADSImporter::new();
587 match importer.import(yaml_content) {
588 Ok(asset) => serde_json::to_string(&asset).map_err(serialization_error),
589 Err(err) => Err(import_error_to_js(err)),
590 }
591 }
592
593 #[wasm_bindgen]
603 pub fn export_to_cads(asset_json: &str) -> Result<String, JsValue> {
604 let asset: crate::models::cads::CADSAsset =
605 serde_json::from_str(asset_json).map_err(deserialization_error)?;
606 let exporter = crate::export::CADSExporter;
607 match exporter.export(&asset) {
608 Ok(yaml) => Ok(yaml),
609 Err(err) => Err(export_error_to_js(err)),
610 }
611 }
612
613 #[wasm_bindgen]
623 pub fn import_from_odps(yaml_content: &str) -> Result<String, JsValue> {
624 let importer = crate::import::ODPSImporter::new();
625 match importer.import(yaml_content) {
626 Ok(product) => serde_json::to_string(&product).map_err(serialization_error),
627 Err(err) => Err(import_error_to_js(err)),
628 }
629 }
630
631 #[wasm_bindgen]
641 pub fn export_to_odps(product_json: &str) -> Result<String, JsValue> {
642 let product: crate::models::odps::ODPSDataProduct =
643 serde_json::from_str(product_json).map_err(deserialization_error)?;
644 let exporter = crate::export::ODPSExporter;
645 match exporter.export(&product) {
646 Ok(yaml) => Ok(yaml),
647 Err(err) => Err(export_error_to_js(err)),
648 }
649 }
650
651 #[cfg(feature = "odps-validation")]
661 #[wasm_bindgen]
662 pub fn validate_odps(yaml_content: &str) -> Result<(), JsValue> {
663 use crate::validation::schema::validate_odps_internal;
664 validate_odps_internal(yaml_content).map_err(validation_error)
665 }
666
667 #[cfg(not(feature = "odps-validation"))]
668 #[wasm_bindgen]
669 pub fn validate_odps(_yaml_content: &str) -> Result<(), JsValue> {
670 Ok(())
673 }
674
675 #[wasm_bindgen]
685 pub fn create_domain(name: &str) -> Result<String, JsValue> {
686 let domain = crate::models::domain::Domain::new(name.to_string());
687 serde_json::to_string(&domain).map_err(serialization_error)
688 }
689
690 #[wasm_bindgen]
700 pub fn import_from_domain(yaml_content: &str) -> Result<String, JsValue> {
701 match crate::models::domain::Domain::from_yaml(yaml_content) {
702 Ok(domain) => serde_json::to_string(&domain).map_err(serialization_error),
703 Err(e) => Err(parse_error(e)),
704 }
705 }
706
707 #[wasm_bindgen]
717 pub fn export_to_domain(domain_json: &str) -> Result<String, JsValue> {
718 let domain: crate::models::domain::Domain =
719 serde_json::from_str(domain_json).map_err(deserialization_error)?;
720 domain.to_yaml().map_err(serialization_error)
721 }
722
723 #[wasm_bindgen]
734 pub fn migrate_dataflow_to_domain(
735 dataflow_yaml: &str,
736 domain_name: Option<String>,
737 ) -> Result<String, JsValue> {
738 match crate::convert::migrate_dataflow::migrate_dataflow_to_domain(
739 dataflow_yaml,
740 domain_name.as_deref(),
741 ) {
742 Ok(domain) => serde_json::to_string(&domain).map_err(serialization_error),
743 Err(e) => Err(conversion_error(e)),
744 }
745 }
746
747 #[wasm_bindgen]
757 pub fn parse_tag(tag_str: &str) -> Result<String, JsValue> {
758 use crate::models::Tag;
759 use std::str::FromStr;
760 match Tag::from_str(tag_str) {
761 Ok(tag) => serde_json::to_string(&tag).map_err(serialization_error),
762 Err(_) => Err(parse_error("Invalid tag format")),
763 }
764 }
765
766 #[wasm_bindgen]
776 pub fn serialize_tag(tag_json: &str) -> Result<String, JsValue> {
777 use crate::models::Tag;
778 let tag: Tag = serde_json::from_str(tag_json).map_err(deserialization_error)?;
779 Ok(tag.to_string())
780 }
781
782 #[wasm_bindgen]
794 pub fn convert_to_odcs(input: &str, format: Option<String>) -> Result<String, JsValue> {
795 match crate::convert::convert_to_odcs(input, format.as_deref()) {
796 Ok(yaml) => Ok(yaml),
797 Err(e) => Err(conversion_error(e)),
798 }
799 }
800
801 #[wasm_bindgen]
812 pub fn filter_nodes_by_owner(workspace_json: &str, owner: &str) -> Result<String, JsValue> {
813 let model = deserialize_workspace(workspace_json)?;
814 let filtered = model.filter_nodes_by_owner(owner);
815 serde_json::to_string(&filtered).map_err(serialization_error)
816 }
817
818 #[wasm_bindgen]
829 pub fn filter_relationships_by_owner(
830 workspace_json: &str,
831 owner: &str,
832 ) -> Result<String, JsValue> {
833 let model = deserialize_workspace(workspace_json)?;
834 let filtered = model.filter_relationships_by_owner(owner);
835 serde_json::to_string(&filtered).map_err(serialization_error)
836 }
837
838 #[wasm_bindgen]
849 pub fn filter_nodes_by_infrastructure_type(
850 workspace_json: &str,
851 infrastructure_type: &str,
852 ) -> Result<String, JsValue> {
853 let model = deserialize_workspace(workspace_json)?;
854 let infra_type: crate::models::enums::InfrastructureType =
855 serde_json::from_str(&format!("\"{}\"", infrastructure_type))
856 .map_err(|e| invalid_input_error("infrastructure type", e))?;
857 let filtered = model.filter_nodes_by_infrastructure_type(infra_type);
858 serde_json::to_string(&filtered).map_err(serialization_error)
859 }
860
861 #[wasm_bindgen]
872 pub fn filter_relationships_by_infrastructure_type(
873 workspace_json: &str,
874 infrastructure_type: &str,
875 ) -> Result<String, JsValue> {
876 let model = deserialize_workspace(workspace_json)?;
877 let infra_type: crate::models::enums::InfrastructureType =
878 serde_json::from_str(&format!("\"{}\"", infrastructure_type))
879 .map_err(|e| invalid_input_error("infrastructure type", e))?;
880 let filtered = model.filter_relationships_by_infrastructure_type(infra_type);
881 serde_json::to_string(&filtered).map_err(serialization_error)
882 }
883
884 #[wasm_bindgen]
895 pub fn filter_by_tags(workspace_json: &str, tag: &str) -> Result<String, JsValue> {
896 let model = deserialize_workspace(workspace_json)?;
897 let (nodes, relationships) = model.filter_by_tags(tag);
898 let result = serde_json::json!({
899 "nodes": nodes,
900 "relationships": relationships
901 });
902 serde_json::to_string(&result).map_err(serialization_error)
903 }
904
905 #[wasm_bindgen]
921 pub fn add_system_to_domain(
922 workspace_json: &str,
923 domain_id: &str,
924 system_json: &str,
925 ) -> Result<String, JsValue> {
926 let mut model = deserialize_workspace(workspace_json)?;
927 let domain_uuid =
928 uuid::Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
929 let system: crate::models::domain::System =
930 serde_json::from_str(system_json).map_err(deserialization_error)?;
931 model
932 .add_system_to_domain(domain_uuid, system)
933 .map_err(|e| WasmError::new("OperationError", e).to_js_value())?;
934 serde_json::to_string(&model).map_err(serialization_error)
935 }
936
937 #[wasm_bindgen]
949 pub fn add_cads_node_to_domain(
950 workspace_json: &str,
951 domain_id: &str,
952 node_json: &str,
953 ) -> Result<String, JsValue> {
954 let mut model = deserialize_workspace(workspace_json)?;
955 let domain_uuid =
956 uuid::Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
957 let node: crate::models::domain::CADSNode =
958 serde_json::from_str(node_json).map_err(deserialization_error)?;
959 model
960 .add_cads_node_to_domain(domain_uuid, node)
961 .map_err(|e| WasmError::new("OperationError", e).to_js_value())?;
962 serde_json::to_string(&model).map_err(serialization_error)
963 }
964
965 #[wasm_bindgen]
977 pub fn add_odcs_node_to_domain(
978 workspace_json: &str,
979 domain_id: &str,
980 node_json: &str,
981 ) -> Result<String, JsValue> {
982 let mut model = deserialize_workspace(workspace_json)?;
983 let domain_uuid =
984 uuid::Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
985 let node: crate::models::domain::ODCSNode =
986 serde_json::from_str(node_json).map_err(deserialization_error)?;
987 model
988 .add_odcs_node_to_domain(domain_uuid, node)
989 .map_err(|e| WasmError::new("OperationError", e).to_js_value())?;
990 serde_json::to_string(&model).map_err(serialization_error)
991 }
992
993 #[wasm_bindgen]
1007 pub fn validate_table_name(name: &str) -> Result<String, JsValue> {
1008 match crate::validation::input::validate_table_name(name) {
1009 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
1010 Err(err) => {
1011 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
1012 }
1013 }
1014 }
1015
1016 #[wasm_bindgen]
1026 pub fn validate_column_name(name: &str) -> Result<String, JsValue> {
1027 match crate::validation::input::validate_column_name(name) {
1028 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
1029 Err(err) => {
1030 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
1031 }
1032 }
1033 }
1034
1035 #[wasm_bindgen]
1045 pub fn validate_uuid(id: &str) -> Result<String, JsValue> {
1046 match crate::validation::input::validate_uuid(id) {
1047 Ok(uuid) => {
1048 Ok(serde_json::json!({"valid": true, "uuid": uuid.to_string()}).to_string())
1049 }
1050 Err(err) => {
1051 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
1052 }
1053 }
1054 }
1055
1056 #[wasm_bindgen]
1066 pub fn validate_data_type(data_type: &str) -> Result<String, JsValue> {
1067 match crate::validation::input::validate_data_type(data_type) {
1068 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
1069 Err(err) => {
1070 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
1071 }
1072 }
1073 }
1074
1075 #[wasm_bindgen]
1085 pub fn validate_description(desc: &str) -> Result<String, JsValue> {
1086 match crate::validation::input::validate_description(desc) {
1087 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
1088 Err(err) => {
1089 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
1090 }
1091 }
1092 }
1093
1094 #[wasm_bindgen]
1105 pub fn sanitize_sql_identifier(name: &str, dialect: &str) -> String {
1106 crate::validation::input::sanitize_sql_identifier(name, dialect)
1107 }
1108
1109 #[wasm_bindgen]
1119 pub fn sanitize_description(desc: &str) -> String {
1120 crate::validation::input::sanitize_description(desc)
1121 }
1122
1123 #[wasm_bindgen]
1134 pub fn detect_naming_conflicts(
1135 existing_tables_json: &str,
1136 new_tables_json: &str,
1137 ) -> Result<String, JsValue> {
1138 let existing_tables: Vec<crate::models::Table> =
1139 serde_json::from_str(existing_tables_json).map_err(deserialization_error)?;
1140 let new_tables: Vec<crate::models::Table> =
1141 serde_json::from_str(new_tables_json).map_err(deserialization_error)?;
1142
1143 let validator = crate::validation::tables::TableValidator::new();
1144 let conflicts = validator.detect_naming_conflicts(&existing_tables, &new_tables);
1145
1146 serde_json::to_string(&conflicts).map_err(serialization_error)
1147 }
1148
1149 #[wasm_bindgen]
1159 pub fn validate_pattern_exclusivity(table_json: &str) -> Result<String, JsValue> {
1160 let table: crate::models::Table =
1161 serde_json::from_str(table_json).map_err(deserialization_error)?;
1162
1163 let validator = crate::validation::tables::TableValidator::new();
1164 match validator.validate_pattern_exclusivity(&table) {
1165 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
1166 Err(violation) => {
1167 Ok(serde_json::json!({"valid": false, "violation": violation}).to_string())
1168 }
1169 }
1170 }
1171
1172 #[wasm_bindgen]
1184 pub fn check_circular_dependency(
1185 relationships_json: &str,
1186 source_table_id: &str,
1187 target_table_id: &str,
1188 ) -> Result<String, JsValue> {
1189 let relationships: Vec<crate::models::Relationship> =
1190 serde_json::from_str(relationships_json).map_err(deserialization_error)?;
1191
1192 let source_id = uuid::Uuid::parse_str(source_table_id)
1193 .map_err(|e| invalid_input_error("source_table_id", e))?;
1194 let target_id = uuid::Uuid::parse_str(target_table_id)
1195 .map_err(|e| invalid_input_error("target_table_id", e))?;
1196
1197 let validator = crate::validation::relationships::RelationshipValidator::new();
1198 match validator.check_circular_dependency(&relationships, source_id, target_id) {
1199 Ok((has_cycle, cycle_path)) => {
1200 let cycle_path_strs: Vec<String> = cycle_path
1201 .map(|path| path.iter().map(|id| id.to_string()).collect())
1202 .unwrap_or_default();
1203 Ok(serde_json::json!({
1204 "has_cycle": has_cycle,
1205 "cycle_path": cycle_path_strs
1206 })
1207 .to_string())
1208 }
1209 Err(err) => Err(validation_error(err)),
1210 }
1211 }
1212
1213 #[wasm_bindgen]
1224 pub fn validate_no_self_reference(
1225 source_table_id: &str,
1226 target_table_id: &str,
1227 ) -> Result<String, JsValue> {
1228 let source_id = uuid::Uuid::parse_str(source_table_id)
1229 .map_err(|e| invalid_input_error("source_table_id", e))?;
1230 let target_id = uuid::Uuid::parse_str(target_table_id)
1231 .map_err(|e| invalid_input_error("target_table_id", e))?;
1232
1233 let validator = crate::validation::relationships::RelationshipValidator::new();
1234 match validator.validate_no_self_reference(source_id, target_id) {
1235 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
1236 Err(self_ref) => {
1237 Ok(serde_json::json!({"valid": false, "self_reference": self_ref}).to_string())
1238 }
1239 }
1240 }
1241
1242 #[cfg(feature = "png-export")]
1258 #[wasm_bindgen]
1259 pub fn export_to_png(workspace_json: &str, width: u32, height: u32) -> Result<String, JsValue> {
1260 let model = deserialize_workspace(workspace_json)?;
1261 let exporter = crate::export::PNGExporter::new();
1262 match exporter.export(&model.tables, width, height) {
1263 Ok(result) => Ok(result.content), Err(err) => Err(export_error_to_js(err)),
1265 }
1266 }
1267
1268 #[wasm_bindgen]
1284 pub fn load_model(db_name: &str, store_name: &str, workspace_path: &str) -> js_sys::Promise {
1285 let db_name = db_name.to_string();
1286 let store_name = store_name.to_string();
1287 let workspace_path = workspace_path.to_string();
1288
1289 wasm_bindgen_futures::future_to_promise(async move {
1290 let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
1291 let loader = crate::model::ModelLoader::new(storage);
1292 match loader.load_model(&workspace_path).await {
1293 Ok(result) => serde_json::to_string(&result)
1294 .map(|s| JsValue::from_str(&s))
1295 .map_err(serialization_error),
1296 Err(err) => Err(storage_error(err)),
1297 }
1298 })
1299 }
1300
1301 #[wasm_bindgen]
1314 pub fn save_model(
1315 db_name: &str,
1316 store_name: &str,
1317 workspace_path: &str,
1318 model_json: &str,
1319 ) -> js_sys::Promise {
1320 let db_name = db_name.to_string();
1321 let store_name = store_name.to_string();
1322 let workspace_path = workspace_path.to_string();
1323 let model_json = model_json.to_string();
1324
1325 wasm_bindgen_futures::future_to_promise(async move {
1326 let model: crate::models::DataModel =
1327 serde_json::from_str(&model_json).map_err(deserialization_error)?;
1328
1329 let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
1330 let saver = crate::model::ModelSaver::new(storage);
1331
1332 for table in &model.tables {
1335 let yaml = crate::export::ODCSExporter::export_table(table, "odcs_v3_1_0");
1337 let table_data = crate::model::saver::TableData {
1338 id: table.id,
1339 name: table.name.clone(),
1340 yaml_file_path: Some(format!("tables/{}.yaml", table.name)),
1341 yaml_value: serde_yaml::from_str(&yaml).map_err(parse_error)?,
1342 };
1343 saver
1344 .save_table(&workspace_path, &table_data)
1345 .await
1346 .map_err(storage_error)?;
1347 }
1348
1349 if !model.relationships.is_empty() {
1351 let rel_data: Vec<crate::model::saver::RelationshipData> = model
1352 .relationships
1353 .iter()
1354 .map(|rel| {
1355 let yaml_value = serde_json::json!({
1356 "id": rel.id.to_string(),
1357 "source_table_id": rel.source_table_id.to_string(),
1358 "target_table_id": rel.target_table_id.to_string(),
1359 });
1360 let yaml_str = serde_json::to_string(&yaml_value)
1362 .map_err(|e| format!("Failed to serialize relationship: {}", e))?;
1363 let yaml_value = serde_yaml::from_str(&yaml_str)
1364 .map_err(|e| format!("Failed to convert to YAML: {}", e))?;
1365 Ok(crate::model::saver::RelationshipData {
1366 id: rel.id,
1367 source_table_id: rel.source_table_id,
1368 target_table_id: rel.target_table_id,
1369 yaml_value,
1370 })
1371 })
1372 .collect::<Result<Vec<_>, String>>()
1373 .map_err(|e| WasmError::new("OperationError", e).to_js_value())?;
1374
1375 saver
1376 .save_relationships(&workspace_path, &rel_data)
1377 .await
1378 .map_err(|e| storage_error(e))?;
1379 }
1380
1381 Ok(JsValue::from_str("Model saved successfully"))
1382 })
1383 }
1384
1385 #[cfg(feature = "bpmn")]
1398 #[wasm_bindgen]
1399 pub fn import_bpmn_model(
1400 domain_id: &str,
1401 xml_content: &str,
1402 model_name: Option<String>,
1403 ) -> Result<String, JsValue> {
1404 use crate::import::bpmn::BPMNImporter;
1405 use uuid::Uuid;
1406
1407 let domain_uuid =
1408 Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
1409
1410 let mut importer = BPMNImporter::new();
1411 match importer.import(xml_content, domain_uuid, model_name.as_deref()) {
1412 Ok(model) => serde_json::to_string(&model).map_err(serialization_error),
1413 Err(e) => Err(import_error_to_js(ImportError::ParseError(e.to_string()))),
1414 }
1415 }
1416
1417 #[cfg(feature = "bpmn")]
1427 #[wasm_bindgen]
1428 pub fn export_bpmn_model(xml_content: &str) -> Result<String, JsValue> {
1429 use crate::export::bpmn::BPMNExporter;
1430 let exporter = BPMNExporter::new();
1431 exporter
1432 .export(xml_content)
1433 .map_err(|e| export_error_to_js(ExportError::SerializationError(e.to_string())))
1434 }
1435
1436 #[cfg(feature = "dmn")]
1449 #[wasm_bindgen]
1450 pub fn import_dmn_model(
1451 domain_id: &str,
1452 xml_content: &str,
1453 model_name: Option<String>,
1454 ) -> Result<String, JsValue> {
1455 use crate::import::dmn::DMNImporter;
1456 use uuid::Uuid;
1457
1458 let domain_uuid =
1459 Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
1460
1461 let mut importer = DMNImporter::new();
1462 match importer.import(xml_content, domain_uuid, model_name.as_deref()) {
1463 Ok(model) => serde_json::to_string(&model).map_err(serialization_error),
1464 Err(e) => Err(import_error_to_js(ImportError::ParseError(e.to_string()))),
1465 }
1466 }
1467
1468 #[cfg(feature = "dmn")]
1478 #[wasm_bindgen]
1479 pub fn export_dmn_model(xml_content: &str) -> Result<String, JsValue> {
1480 use crate::export::dmn::DMNExporter;
1481 let exporter = DMNExporter::new();
1482 exporter
1483 .export(xml_content)
1484 .map_err(|e| export_error_to_js(ExportError::SerializationError(e.to_string())))
1485 }
1486
1487 #[cfg(feature = "openapi")]
1500 #[wasm_bindgen]
1501 pub fn import_openapi_spec(
1502 domain_id: &str,
1503 content: &str,
1504 api_name: Option<String>,
1505 ) -> Result<String, JsValue> {
1506 use crate::import::openapi::OpenAPIImporter;
1507 use uuid::Uuid;
1508
1509 let domain_uuid =
1510 Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
1511
1512 let mut importer = OpenAPIImporter::new();
1513 match importer.import(content, domain_uuid, api_name.as_deref()) {
1514 Ok(model) => serde_json::to_string(&model).map_err(serialization_error),
1515 Err(e) => Err(import_error_to_js(ImportError::ParseError(e.to_string()))),
1516 }
1517 }
1518
1519 #[cfg(feature = "openapi")]
1531 #[wasm_bindgen]
1532 pub fn export_openapi_spec(
1533 content: &str,
1534 source_format: &str,
1535 target_format: Option<String>,
1536 ) -> Result<String, JsValue> {
1537 use crate::export::openapi::OpenAPIExporter;
1538 use crate::models::openapi::OpenAPIFormat;
1539
1540 let source_fmt = match source_format {
1541 "yaml" | "yml" => OpenAPIFormat::Yaml,
1542 "json" => OpenAPIFormat::Json,
1543 _ => {
1544 return Err(invalid_input_error("source format", "Use 'yaml' or 'json'"));
1545 }
1546 };
1547
1548 let target_fmt = if let Some(tf) = target_format {
1549 match tf.as_str() {
1550 "yaml" | "yml" => Some(OpenAPIFormat::Yaml),
1551 "json" => Some(OpenAPIFormat::Json),
1552 _ => {
1553 return Err(invalid_input_error("target format", "Use 'yaml' or 'json'"));
1554 }
1555 }
1556 } else {
1557 None
1558 };
1559
1560 let exporter = OpenAPIExporter::new();
1561 exporter
1562 .export(content, source_fmt, target_fmt)
1563 .map_err(|e| export_error_to_js(ExportError::SerializationError(e.to_string())))
1564 }
1565
1566 #[cfg(feature = "openapi")]
1578 #[wasm_bindgen]
1579 pub fn convert_openapi_to_odcs(
1580 openapi_content: &str,
1581 component_name: &str,
1582 table_name: Option<String>,
1583 ) -> Result<String, JsValue> {
1584 use crate::convert::openapi_to_odcs::OpenAPIToODCSConverter;
1585
1586 let converter = OpenAPIToODCSConverter::new();
1587 match converter.convert_component(openapi_content, component_name, table_name.as_deref()) {
1588 Ok(table) => serde_json::to_string(&table).map_err(serialization_error),
1589 Err(e) => Err(conversion_error(e)),
1590 }
1591 }
1592
1593 #[cfg(feature = "openapi")]
1604 #[wasm_bindgen]
1605 pub fn analyze_openapi_conversion(
1606 openapi_content: &str,
1607 component_name: &str,
1608 ) -> Result<String, JsValue> {
1609 use crate::convert::openapi_to_odcs::OpenAPIToODCSConverter;
1610
1611 let converter = OpenAPIToODCSConverter::new();
1612 match converter.analyze_conversion(openapi_content, component_name) {
1613 Ok(report) => serde_json::to_string(&report).map_err(serialization_error),
1614 Err(e) => Err(WasmError::new("AnalysisError", e.to_string())
1615 .with_code("ANALYSIS_FAILED")
1616 .to_js_value()),
1617 }
1618 }
1619
1620 #[wasm_bindgen]
1635 pub fn create_workspace(name: &str, owner_id: &str) -> Result<String, JsValue> {
1636 use crate::models::workspace::Workspace;
1637 use uuid::Uuid;
1638
1639 let owner_uuid =
1640 Uuid::parse_str(owner_id).map_err(|e| invalid_input_error("owner ID", e))?;
1641
1642 let workspace = Workspace::new(name.to_string(), owner_uuid);
1643
1644 serde_json::to_string(&workspace).map_err(serialization_error)
1645 }
1646
1647 #[wasm_bindgen]
1657 pub fn parse_workspace_yaml(yaml_content: &str) -> Result<String, JsValue> {
1658 use crate::models::workspace::Workspace;
1659
1660 let workspace: Workspace = serde_yaml::from_str(yaml_content).map_err(parse_error)?;
1661 serde_json::to_string(&workspace).map_err(serialization_error)
1662 }
1663
1664 #[wasm_bindgen]
1674 pub fn export_workspace_to_yaml(workspace_json: &str) -> Result<String, JsValue> {
1675 use crate::models::workspace::Workspace;
1676
1677 let workspace: Workspace =
1678 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1679 serde_yaml::to_string(&workspace).map_err(serialization_error)
1680 }
1681
1682 #[wasm_bindgen]
1694 pub fn add_domain_to_workspace(
1695 workspace_json: &str,
1696 domain_id: &str,
1697 domain_name: &str,
1698 ) -> Result<String, JsValue> {
1699 use crate::models::workspace::{DomainReference, Workspace};
1700 use chrono::Utc;
1701 use uuid::Uuid;
1702
1703 let mut workspace: Workspace =
1704 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1705 let domain_uuid =
1706 Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
1707
1708 if workspace.domains.iter().any(|d| d.id == domain_uuid) {
1710 return Err(WasmError::new(
1711 "DuplicateError",
1712 format!("Domain {} already exists in workspace", domain_id),
1713 )
1714 .with_code("DUPLICATE_DOMAIN")
1715 .to_js_value());
1716 }
1717
1718 workspace.domains.push(DomainReference {
1719 id: domain_uuid,
1720 name: domain_name.to_string(),
1721 description: None,
1722 systems: Vec::new(),
1723 view_positions: std::collections::HashMap::new(),
1724 });
1725 workspace.last_modified_at = Utc::now();
1726
1727 serde_json::to_string(&workspace).map_err(serialization_error)
1728 }
1729
1730 #[wasm_bindgen]
1741 pub fn remove_domain_from_workspace(
1742 workspace_json: &str,
1743 domain_id: &str,
1744 ) -> Result<String, JsValue> {
1745 use crate::models::workspace::Workspace;
1746 use chrono::Utc;
1747 use uuid::Uuid;
1748
1749 let mut workspace: Workspace =
1750 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1751 let domain_uuid =
1752 Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
1753
1754 let original_len = workspace.domains.len();
1755 workspace.domains.retain(|d| d.id != domain_uuid);
1756
1757 if workspace.domains.len() == original_len {
1758 return Err(WasmError::new(
1759 "NotFoundError",
1760 format!("Domain {} not found in workspace", domain_id),
1761 )
1762 .with_code("DOMAIN_NOT_FOUND")
1763 .to_js_value());
1764 }
1765
1766 workspace.last_modified_at = Utc::now();
1767 serde_json::to_string(&workspace).map_err(serialization_error)
1768 }
1769
1770 #[wasm_bindgen]
1781 pub fn add_relationship_to_workspace(
1782 workspace_json: &str,
1783 relationship_json: &str,
1784 ) -> Result<String, JsValue> {
1785 use crate::models::Relationship;
1786 use crate::models::workspace::Workspace;
1787
1788 let mut workspace: Workspace =
1789 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1790 let relationship: Relationship =
1791 serde_json::from_str(relationship_json).map_err(deserialization_error)?;
1792
1793 if workspace
1795 .relationships
1796 .iter()
1797 .any(|r| r.id == relationship.id)
1798 {
1799 return Err(WasmError::new(
1800 "DuplicateError",
1801 format!(
1802 "Relationship {} already exists in workspace",
1803 relationship.id
1804 ),
1805 )
1806 .with_code("DUPLICATE_RELATIONSHIP")
1807 .to_js_value());
1808 }
1809
1810 workspace.add_relationship(relationship);
1811 serde_json::to_string(&workspace).map_err(serialization_error)
1812 }
1813
1814 #[wasm_bindgen]
1825 pub fn remove_relationship_from_workspace(
1826 workspace_json: &str,
1827 relationship_id: &str,
1828 ) -> Result<String, JsValue> {
1829 use crate::models::workspace::Workspace;
1830 use uuid::Uuid;
1831
1832 let mut workspace: Workspace =
1833 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1834 let relationship_uuid = Uuid::parse_str(relationship_id)
1835 .map_err(|e| invalid_input_error("relationship ID", e))?;
1836
1837 if !workspace.remove_relationship(relationship_uuid) {
1838 return Err(WasmError::new(
1839 "NotFoundError",
1840 format!("Relationship {} not found in workspace", relationship_id),
1841 )
1842 .with_code("RELATIONSHIP_NOT_FOUND")
1843 .to_js_value());
1844 }
1845
1846 serde_json::to_string(&workspace).map_err(serialization_error)
1847 }
1848
1849 #[wasm_bindgen]
1860 pub fn get_workspace_relationships_for_source(
1861 workspace_json: &str,
1862 source_table_id: &str,
1863 ) -> Result<String, JsValue> {
1864 use crate::models::workspace::Workspace;
1865 use uuid::Uuid;
1866
1867 let workspace: Workspace =
1868 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1869 let source_uuid = Uuid::parse_str(source_table_id)
1870 .map_err(|e| invalid_input_error("source table ID", e))?;
1871
1872 let relationships: Vec<_> = workspace.get_relationships_for_source(source_uuid);
1873 serde_json::to_string(&relationships).map_err(serialization_error)
1874 }
1875
1876 #[wasm_bindgen]
1887 pub fn get_workspace_relationships_for_target(
1888 workspace_json: &str,
1889 target_table_id: &str,
1890 ) -> Result<String, JsValue> {
1891 use crate::models::workspace::Workspace;
1892 use uuid::Uuid;
1893
1894 let workspace: Workspace =
1895 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1896 let target_uuid = Uuid::parse_str(target_table_id)
1897 .map_err(|e| invalid_input_error("target table ID", e))?;
1898
1899 let relationships: Vec<_> = workspace.get_relationships_for_target(target_uuid);
1900 serde_json::to_string(&relationships).map_err(serialization_error)
1901 }
1902
1903 #[wasm_bindgen]
1914 pub fn create_domain_config(name: &str, workspace_id: &str) -> Result<String, JsValue> {
1915 use crate::models::domain_config::DomainConfig;
1916 use chrono::Utc;
1917 use std::collections::HashMap;
1918 use uuid::Uuid;
1919
1920 let workspace_uuid =
1921 Uuid::parse_str(workspace_id).map_err(|e| invalid_input_error("workspace ID", e))?;
1922
1923 let config = DomainConfig {
1924 id: Uuid::new_v4(),
1925 workspace_id: workspace_uuid,
1926 name: name.to_string(),
1927 description: None,
1928 created_at: Utc::now(),
1929 last_modified_at: Utc::now(),
1930 owner: None,
1931 systems: Vec::new(),
1932 tables: Vec::new(),
1933 products: Vec::new(),
1934 assets: Vec::new(),
1935 processes: Vec::new(),
1936 decisions: Vec::new(),
1937 view_positions: HashMap::new(),
1938 folder_path: None,
1939 workspace_path: None,
1940 };
1941
1942 serde_json::to_string(&config).map_err(serialization_error)
1943 }
1944
1945 #[wasm_bindgen]
1955 pub fn parse_domain_config_yaml(yaml_content: &str) -> Result<String, JsValue> {
1956 use crate::models::domain_config::DomainConfig;
1957
1958 let config: DomainConfig = serde_yaml::from_str(yaml_content).map_err(parse_error)?;
1959 serde_json::to_string(&config).map_err(serialization_error)
1960 }
1961
1962 #[wasm_bindgen]
1972 pub fn export_domain_config_to_yaml(config_json: &str) -> Result<String, JsValue> {
1973 use crate::models::domain_config::DomainConfig;
1974
1975 let config: DomainConfig =
1976 serde_json::from_str(config_json).map_err(deserialization_error)?;
1977 serde_yaml::to_string(&config).map_err(serialization_error)
1978 }
1979
1980 #[wasm_bindgen]
1990 pub fn get_domain_config_id(config_json: &str) -> Result<String, JsValue> {
1991 use crate::models::domain_config::DomainConfig;
1992
1993 let config: DomainConfig =
1994 serde_json::from_str(config_json).map_err(deserialization_error)?;
1995 Ok(config.id.to_string())
1996 }
1997
1998 #[wasm_bindgen]
2009 pub fn update_domain_view_positions(
2010 config_json: &str,
2011 positions_json: &str,
2012 ) -> Result<String, JsValue> {
2013 use crate::models::domain_config::{DomainConfig, ViewPosition};
2014 use chrono::Utc;
2015 use std::collections::HashMap;
2016
2017 let mut config: DomainConfig =
2018 serde_json::from_str(config_json).map_err(deserialization_error)?;
2019 let positions: HashMap<String, HashMap<String, ViewPosition>> =
2020 serde_json::from_str(positions_json).map_err(deserialization_error)?;
2021
2022 config.view_positions = positions;
2023 config.last_modified_at = Utc::now();
2024
2025 serde_json::to_string(&config).map_err(serialization_error)
2026 }
2027
2028 #[wasm_bindgen]
2040 pub fn add_entity_to_domain_config(
2041 config_json: &str,
2042 entity_type: &str,
2043 entity_id: &str,
2044 ) -> Result<String, JsValue> {
2045 use crate::models::domain_config::DomainConfig;
2046 use chrono::Utc;
2047 use uuid::Uuid;
2048
2049 let mut config: DomainConfig =
2050 serde_json::from_str(config_json).map_err(deserialization_error)?;
2051 let entity_uuid =
2052 Uuid::parse_str(entity_id).map_err(|e| invalid_input_error("entity ID", e))?;
2053
2054 let entities = match entity_type {
2055 "system" => &mut config.systems,
2056 "table" => &mut config.tables,
2057 "product" => &mut config.products,
2058 "asset" => &mut config.assets,
2059 "process" => &mut config.processes,
2060 "decision" => &mut config.decisions,
2061 _ => {
2062 return Err(invalid_input_error(
2063 "entity type",
2064 "Use 'system', 'table', 'product', 'asset', 'process', or 'decision'",
2065 ));
2066 }
2067 };
2068
2069 if entities.contains(&entity_uuid) {
2070 return Err(WasmError::new(
2071 "DuplicateError",
2072 format!(
2073 "{} {} already exists in domain config",
2074 entity_type, entity_id
2075 ),
2076 )
2077 .with_code("DUPLICATE_ENTITY")
2078 .to_js_value());
2079 }
2080
2081 entities.push(entity_uuid);
2082 config.last_modified_at = Utc::now();
2083
2084 serde_json::to_string(&config).map_err(serialization_error)
2085 }
2086
2087 #[wasm_bindgen]
2099 pub fn remove_entity_from_domain_config(
2100 config_json: &str,
2101 entity_type: &str,
2102 entity_id: &str,
2103 ) -> Result<String, JsValue> {
2104 use crate::models::domain_config::DomainConfig;
2105 use chrono::Utc;
2106 use uuid::Uuid;
2107
2108 let mut config: DomainConfig =
2109 serde_json::from_str(config_json).map_err(deserialization_error)?;
2110 let entity_uuid =
2111 Uuid::parse_str(entity_id).map_err(|e| invalid_input_error("entity ID", e))?;
2112
2113 let entities = match entity_type {
2114 "system" => &mut config.systems,
2115 "table" => &mut config.tables,
2116 "product" => &mut config.products,
2117 "asset" => &mut config.assets,
2118 "process" => &mut config.processes,
2119 "decision" => &mut config.decisions,
2120 _ => {
2121 return Err(invalid_input_error(
2122 "entity type",
2123 "Use 'system', 'table', 'product', 'asset', 'process', or 'decision'",
2124 ));
2125 }
2126 };
2127
2128 let original_len = entities.len();
2129 entities.retain(|id| *id != entity_uuid);
2130
2131 if entities.len() == original_len {
2132 return Err(WasmError::new(
2133 "NotFoundError",
2134 format!("{} {} not found in domain config", entity_type, entity_id),
2135 )
2136 .with_code("ENTITY_NOT_FOUND")
2137 .to_js_value());
2138 }
2139
2140 config.last_modified_at = Utc::now();
2141 serde_json::to_string(&config).map_err(serialization_error)
2142 }
2143
2144 #[wasm_bindgen]
2158 pub fn parse_decision_yaml(yaml_content: &str) -> Result<String, JsValue> {
2159 use crate::import::decision::DecisionImporter;
2160
2161 let importer = DecisionImporter::new();
2162 match importer.import(yaml_content) {
2163 Ok(decision) => serde_json::to_string(&decision).map_err(serialization_error),
2164 Err(e) => Err(import_error_to_js(e)),
2165 }
2166 }
2167
2168 #[wasm_bindgen]
2178 pub fn parse_decision_index_yaml(yaml_content: &str) -> Result<String, JsValue> {
2179 use crate::import::decision::DecisionImporter;
2180
2181 let importer = DecisionImporter::new();
2182 match importer.import_index(yaml_content) {
2183 Ok(index) => serde_json::to_string(&index).map_err(serialization_error),
2184 Err(e) => Err(import_error_to_js(e)),
2185 }
2186 }
2187
2188 #[wasm_bindgen]
2198 pub fn export_decision_to_yaml(decision_json: &str) -> Result<String, JsValue> {
2199 use crate::export::decision::DecisionExporter;
2200 use crate::models::decision::Decision;
2201
2202 let decision: Decision =
2203 serde_json::from_str(decision_json).map_err(deserialization_error)?;
2204 let exporter = DecisionExporter::new();
2205 exporter
2206 .export_without_validation(&decision)
2207 .map_err(export_error_to_js)
2208 }
2209
2210 #[wasm_bindgen]
2220 pub fn export_decision_index_to_yaml(index_json: &str) -> Result<String, JsValue> {
2221 use crate::export::decision::DecisionExporter;
2222 use crate::models::decision::DecisionIndex;
2223
2224 let index: DecisionIndex =
2225 serde_json::from_str(index_json).map_err(deserialization_error)?;
2226 let exporter = DecisionExporter::new();
2227 exporter.export_index(&index).map_err(export_error_to_js)
2228 }
2229
2230 #[wasm_bindgen]
2240 pub fn export_decision_to_markdown(decision_json: &str) -> Result<String, JsValue> {
2241 use crate::export::markdown::MarkdownExporter;
2242 use crate::models::decision::Decision;
2243
2244 let decision: Decision =
2245 serde_json::from_str(decision_json).map_err(deserialization_error)?;
2246 let exporter = MarkdownExporter::new();
2247 exporter
2248 .export_decision(&decision)
2249 .map_err(export_error_to_js)
2250 }
2251
2252 #[wasm_bindgen]
2265 pub fn create_decision(
2266 number: u32,
2267 title: &str,
2268 context: &str,
2269 decision: &str,
2270 ) -> Result<String, JsValue> {
2271 use crate::models::decision::Decision;
2272
2273 let dec = Decision::new(number.into(), title, context, decision);
2274 serde_json::to_string(&dec).map_err(serialization_error)
2275 }
2276
2277 #[wasm_bindgen]
2283 pub fn create_decision_index() -> Result<String, JsValue> {
2284 use crate::models::decision::DecisionIndex;
2285
2286 let index = DecisionIndex::new();
2287 serde_json::to_string(&index).map_err(serialization_error)
2288 }
2289
2290 #[wasm_bindgen]
2302 pub fn add_decision_to_index(
2303 index_json: &str,
2304 decision_json: &str,
2305 filename: &str,
2306 ) -> Result<String, JsValue> {
2307 use crate::models::decision::{Decision, DecisionIndex};
2308
2309 let mut index: DecisionIndex =
2310 serde_json::from_str(index_json).map_err(deserialization_error)?;
2311 let decision: Decision =
2312 serde_json::from_str(decision_json).map_err(deserialization_error)?;
2313
2314 index.add_decision(&decision, filename.to_string());
2315 serde_json::to_string(&index).map_err(serialization_error)
2316 }
2317
2318 #[wasm_bindgen]
2332 pub fn parse_knowledge_yaml(yaml_content: &str) -> Result<String, JsValue> {
2333 use crate::import::knowledge::KnowledgeImporter;
2334
2335 let importer = KnowledgeImporter::new();
2336 match importer.import(yaml_content) {
2337 Ok(article) => serde_json::to_string(&article).map_err(serialization_error),
2338 Err(e) => Err(import_error_to_js(e)),
2339 }
2340 }
2341
2342 #[wasm_bindgen]
2352 pub fn parse_knowledge_index_yaml(yaml_content: &str) -> Result<String, JsValue> {
2353 use crate::import::knowledge::KnowledgeImporter;
2354
2355 let importer = KnowledgeImporter::new();
2356 match importer.import_index(yaml_content) {
2357 Ok(index) => serde_json::to_string(&index).map_err(serialization_error),
2358 Err(e) => Err(import_error_to_js(e)),
2359 }
2360 }
2361
2362 #[wasm_bindgen]
2372 pub fn export_knowledge_to_yaml(article_json: &str) -> Result<String, JsValue> {
2373 use crate::export::knowledge::KnowledgeExporter;
2374 use crate::models::knowledge::KnowledgeArticle;
2375
2376 let article: KnowledgeArticle =
2377 serde_json::from_str(article_json).map_err(deserialization_error)?;
2378 let exporter = KnowledgeExporter::new();
2379 exporter
2380 .export_without_validation(&article)
2381 .map_err(export_error_to_js)
2382 }
2383
2384 #[wasm_bindgen]
2394 pub fn export_knowledge_index_to_yaml(index_json: &str) -> Result<String, JsValue> {
2395 use crate::export::knowledge::KnowledgeExporter;
2396 use crate::models::knowledge::KnowledgeIndex;
2397
2398 let index: KnowledgeIndex =
2399 serde_json::from_str(index_json).map_err(deserialization_error)?;
2400 let exporter = KnowledgeExporter::new();
2401 exporter.export_index(&index).map_err(export_error_to_js)
2402 }
2403
2404 #[wasm_bindgen]
2414 pub fn export_knowledge_to_markdown(article_json: &str) -> Result<String, JsValue> {
2415 use crate::export::markdown::MarkdownExporter;
2416 use crate::models::knowledge::KnowledgeArticle;
2417
2418 let article: KnowledgeArticle =
2419 serde_json::from_str(article_json).map_err(deserialization_error)?;
2420 let exporter = MarkdownExporter::new();
2421 exporter
2422 .export_knowledge(&article)
2423 .map_err(export_error_to_js)
2424 }
2425
2426 #[wasm_bindgen]
2440 pub fn create_knowledge_article(
2441 number: u32,
2442 title: &str,
2443 summary: &str,
2444 content: &str,
2445 author: &str,
2446 ) -> Result<String, JsValue> {
2447 use crate::models::knowledge::KnowledgeArticle;
2448
2449 let article = KnowledgeArticle::new(number.into(), title, summary, content, author);
2450 serde_json::to_string(&article).map_err(serialization_error)
2451 }
2452
2453 #[wasm_bindgen]
2459 pub fn create_knowledge_index() -> Result<String, JsValue> {
2460 use crate::models::knowledge::KnowledgeIndex;
2461
2462 let index = KnowledgeIndex::new();
2463 serde_json::to_string(&index).map_err(serialization_error)
2464 }
2465
2466 #[wasm_bindgen]
2478 pub fn add_article_to_knowledge_index(
2479 index_json: &str,
2480 article_json: &str,
2481 filename: &str,
2482 ) -> Result<String, JsValue> {
2483 use crate::models::knowledge::{KnowledgeArticle, KnowledgeIndex};
2484
2485 let mut index: KnowledgeIndex =
2486 serde_json::from_str(index_json).map_err(deserialization_error)?;
2487 let article: KnowledgeArticle =
2488 serde_json::from_str(article_json).map_err(deserialization_error)?;
2489
2490 index.add_article(&article, filename.to_string());
2491 serde_json::to_string(&index).map_err(serialization_error)
2492 }
2493
2494 #[wasm_bindgen]
2505 pub fn search_knowledge_articles(articles_json: &str, query: &str) -> Result<String, JsValue> {
2506 use crate::models::knowledge::KnowledgeArticle;
2507
2508 let articles: Vec<KnowledgeArticle> =
2509 serde_json::from_str(articles_json).map_err(deserialization_error)?;
2510
2511 let query_lower = query.to_lowercase();
2512 let matches: Vec<&KnowledgeArticle> = articles
2513 .iter()
2514 .filter(|article| {
2515 article.title.to_lowercase().contains(&query_lower)
2516 || article.summary.to_lowercase().contains(&query_lower)
2517 || article.content.to_lowercase().contains(&query_lower)
2518 || article
2519 .tags
2520 .iter()
2521 .any(|tag| tag.to_string().to_lowercase().contains(&query_lower))
2522 })
2523 .collect();
2524
2525 serde_json::to_string(&matches).map_err(serialization_error)
2526 }
2527
2528 #[wasm_bindgen]
2541 pub fn export_decision_to_pdf(
2542 decision_json: &str,
2543 branding_json: Option<String>,
2544 ) -> Result<String, JsValue> {
2545 use crate::export::pdf::{BrandingConfig, PdfExporter};
2546 use crate::models::decision::Decision;
2547
2548 let decision: Decision =
2549 serde_json::from_str(decision_json).map_err(deserialization_error)?;
2550
2551 let exporter = if let Some(branding_str) = branding_json {
2552 let branding: BrandingConfig =
2553 serde_json::from_str(&branding_str).map_err(deserialization_error)?;
2554 PdfExporter::with_branding(branding)
2555 } else {
2556 PdfExporter::new()
2557 };
2558
2559 let result = exporter
2560 .export_decision(&decision)
2561 .map_err(export_error_to_js)?;
2562
2563 serde_json::to_string(&result).map_err(serialization_error)
2564 }
2565
2566 #[wasm_bindgen]
2577 pub fn export_knowledge_to_pdf(
2578 article_json: &str,
2579 branding_json: Option<String>,
2580 ) -> Result<String, JsValue> {
2581 use crate::export::pdf::{BrandingConfig, PdfExporter};
2582 use crate::models::knowledge::KnowledgeArticle;
2583
2584 let article: KnowledgeArticle =
2585 serde_json::from_str(article_json).map_err(deserialization_error)?;
2586
2587 let exporter = if let Some(branding_str) = branding_json {
2588 let branding: BrandingConfig =
2589 serde_json::from_str(&branding_str).map_err(deserialization_error)?;
2590 PdfExporter::with_branding(branding)
2591 } else {
2592 PdfExporter::new()
2593 };
2594
2595 let result = exporter
2596 .export_knowledge(&article)
2597 .map_err(export_error_to_js)?;
2598
2599 serde_json::to_string(&result).map_err(serialization_error)
2600 }
2601
2602 #[wasm_bindgen]
2615 pub fn export_markdown_to_pdf(
2616 title: &str,
2617 content: &str,
2618 filename: &str,
2619 branding_json: Option<String>,
2620 ) -> Result<String, JsValue> {
2621 use crate::export::pdf::{BrandingConfig, PdfExporter};
2622
2623 let exporter = if let Some(branding_str) = branding_json {
2624 let branding: BrandingConfig =
2625 serde_json::from_str(&branding_str).map_err(deserialization_error)?;
2626 PdfExporter::with_branding(branding)
2627 } else {
2628 PdfExporter::new()
2629 };
2630
2631 let result = exporter
2632 .export_markdown(title, content, filename)
2633 .map_err(export_error_to_js)?;
2634
2635 serde_json::to_string(&result).map_err(serialization_error)
2636 }
2637
2638 #[wasm_bindgen]
2651 pub fn export_decision_to_branded_markdown(
2652 decision_json: &str,
2653 branding_json: Option<String>,
2654 ) -> Result<String, JsValue> {
2655 use crate::export::markdown::{BrandedMarkdownExporter, MarkdownBrandingConfig};
2656 use crate::models::decision::Decision;
2657
2658 let decision: Decision =
2659 serde_json::from_str(decision_json).map_err(deserialization_error)?;
2660
2661 let exporter = if let Some(branding_str) = branding_json {
2662 let branding: MarkdownBrandingConfig =
2663 serde_json::from_str(&branding_str).map_err(deserialization_error)?;
2664 BrandedMarkdownExporter::with_branding(branding)
2665 } else {
2666 BrandedMarkdownExporter::new()
2667 };
2668
2669 exporter
2670 .export_decision(&decision)
2671 .map_err(export_error_to_js)
2672 }
2673
2674 #[wasm_bindgen]
2685 pub fn export_knowledge_to_branded_markdown(
2686 article_json: &str,
2687 branding_json: Option<String>,
2688 ) -> Result<String, JsValue> {
2689 use crate::export::markdown::{BrandedMarkdownExporter, MarkdownBrandingConfig};
2690 use crate::models::knowledge::KnowledgeArticle;
2691
2692 let article: KnowledgeArticle =
2693 serde_json::from_str(article_json).map_err(deserialization_error)?;
2694
2695 let exporter = if let Some(branding_str) = branding_json {
2696 let branding: MarkdownBrandingConfig =
2697 serde_json::from_str(&branding_str).map_err(deserialization_error)?;
2698 BrandedMarkdownExporter::with_branding(branding)
2699 } else {
2700 BrandedMarkdownExporter::new()
2701 };
2702
2703 exporter
2704 .export_knowledge(&article)
2705 .map_err(export_error_to_js)
2706 }
2707
2708 #[wasm_bindgen]
2714 pub fn get_default_pdf_branding() -> Result<String, JsValue> {
2715 use crate::export::pdf::BrandingConfig;
2716
2717 let config = BrandingConfig::default();
2718 serde_json::to_string(&config).map_err(serialization_error)
2719 }
2720
2721 #[wasm_bindgen]
2727 pub fn get_default_markdown_branding() -> Result<String, JsValue> {
2728 use crate::export::markdown::MarkdownBrandingConfig;
2729
2730 let config = MarkdownBrandingConfig::default();
2731 serde_json::to_string(&config).map_err(serialization_error)
2732 }
2733
2734 #[wasm_bindgen]
2749 pub fn export_table_to_pdf(
2750 table_json: &str,
2751 branding_json: Option<String>,
2752 ) -> Result<String, JsValue> {
2753 use crate::export::pdf::{BrandingConfig, PdfExporter};
2754 use crate::models::Table;
2755
2756 let table: Table = serde_json::from_str(table_json).map_err(deserialization_error)?;
2757
2758 let exporter = if let Some(branding_str) = branding_json {
2759 let branding: BrandingConfig =
2760 serde_json::from_str(&branding_str).map_err(deserialization_error)?;
2761 PdfExporter::with_branding(branding)
2762 } else {
2763 PdfExporter::new()
2764 };
2765
2766 let result = exporter.export_table(&table).map_err(export_error_to_js)?;
2767
2768 serde_json::to_string(&result).map_err(serialization_error)
2769 }
2770
2771 #[wasm_bindgen]
2782 pub fn export_odps_to_pdf(
2783 product_json: &str,
2784 branding_json: Option<String>,
2785 ) -> Result<String, JsValue> {
2786 use crate::export::pdf::{BrandingConfig, PdfExporter};
2787 use crate::models::odps::ODPSDataProduct;
2788
2789 let product: ODPSDataProduct =
2790 serde_json::from_str(product_json).map_err(deserialization_error)?;
2791
2792 let exporter = if let Some(branding_str) = branding_json {
2793 let branding: BrandingConfig =
2794 serde_json::from_str(&branding_str).map_err(deserialization_error)?;
2795 PdfExporter::with_branding(branding)
2796 } else {
2797 PdfExporter::new()
2798 };
2799
2800 let result = exporter
2801 .export_data_product(&product)
2802 .map_err(export_error_to_js)?;
2803
2804 serde_json::to_string(&result).map_err(serialization_error)
2805 }
2806
2807 #[wasm_bindgen]
2818 pub fn export_cads_to_pdf(
2819 asset_json: &str,
2820 branding_json: Option<String>,
2821 ) -> Result<String, JsValue> {
2822 use crate::export::pdf::{BrandingConfig, PdfExporter};
2823 use crate::models::cads::CADSAsset;
2824
2825 let asset: CADSAsset = serde_json::from_str(asset_json).map_err(deserialization_error)?;
2826
2827 let exporter = if let Some(branding_str) = branding_json {
2828 let branding: BrandingConfig =
2829 serde_json::from_str(&branding_str).map_err(deserialization_error)?;
2830 PdfExporter::with_branding(branding)
2831 } else {
2832 PdfExporter::new()
2833 };
2834
2835 let result = exporter
2836 .export_cads_asset(&asset)
2837 .map_err(export_error_to_js)?;
2838
2839 serde_json::to_string(&result).map_err(serialization_error)
2840 }
2841
2842 #[wasm_bindgen]
2852 pub fn export_table_to_markdown(table_json: &str) -> Result<String, JsValue> {
2853 use crate::export::pdf::PdfExporter;
2854 use crate::models::Table;
2855
2856 let table: Table = serde_json::from_str(table_json).map_err(deserialization_error)?;
2857
2858 let exporter = PdfExporter::new();
2860 Ok(exporter.table_to_markdown_public(&table))
2861 }
2862
2863 #[wasm_bindgen]
2873 pub fn export_odps_to_markdown(product_json: &str) -> Result<String, JsValue> {
2874 use crate::export::pdf::PdfExporter;
2875 use crate::models::odps::ODPSDataProduct;
2876
2877 let product: ODPSDataProduct =
2878 serde_json::from_str(product_json).map_err(deserialization_error)?;
2879
2880 let exporter = PdfExporter::new();
2881 Ok(exporter.data_product_to_markdown_public(&product))
2882 }
2883
2884 #[wasm_bindgen]
2894 pub fn export_cads_to_markdown(asset_json: &str) -> Result<String, JsValue> {
2895 use crate::export::pdf::PdfExporter;
2896 use crate::models::cads::CADSAsset;
2897
2898 let asset: CADSAsset = serde_json::from_str(asset_json).map_err(deserialization_error)?;
2899
2900 let exporter = PdfExporter::new();
2901 Ok(exporter.cads_asset_to_markdown_public(&asset))
2902 }
2903
2904 #[wasm_bindgen]
2922 pub fn export_odcs_yaml_to_pdf(
2923 odcs_yaml: &str,
2924 branding_json: Option<String>,
2925 ) -> Result<String, JsValue> {
2926 use crate::export::pdf::{BrandingConfig, PdfExporter};
2927 use crate::import::ODCSImporter;
2928
2929 let mut importer = ODCSImporter::new();
2931 let import_result = importer.import(odcs_yaml).map_err(import_error_to_js)?;
2932
2933 if import_result.tables.is_empty() {
2934 return Err(
2935 WasmError::new("ImportError", "ODCS content contains no tables")
2936 .with_code("NO_TABLES")
2937 .to_js_value(),
2938 );
2939 }
2940
2941 let table_data = &import_result.tables[0];
2943 let table = crate::models::Table::from_table_data(table_data);
2944
2945 let exporter = if let Some(branding_str) = branding_json {
2946 let branding: BrandingConfig =
2947 serde_json::from_str(&branding_str).map_err(deserialization_error)?;
2948 PdfExporter::with_branding(branding)
2949 } else {
2950 PdfExporter::new()
2951 };
2952
2953 let result = exporter.export_table(&table).map_err(export_error_to_js)?;
2954 serde_json::to_string(&result).map_err(serialization_error)
2955 }
2956
2957 #[wasm_bindgen]
2969 pub fn export_odcs_yaml_to_markdown(odcs_yaml: &str) -> Result<String, JsValue> {
2970 use crate::export::pdf::PdfExporter;
2971 use crate::import::ODCSImporter;
2972
2973 let mut importer = ODCSImporter::new();
2974 let import_result = importer.import(odcs_yaml).map_err(import_error_to_js)?;
2975
2976 if import_result.tables.is_empty() {
2977 return Err(
2978 WasmError::new("ImportError", "ODCS content contains no tables")
2979 .with_code("NO_TABLES")
2980 .to_js_value(),
2981 );
2982 }
2983
2984 let table_data = &import_result.tables[0];
2985 let table = crate::models::Table::from_table_data(table_data);
2986
2987 let exporter = PdfExporter::new();
2988 Ok(exporter.table_to_markdown_public(&table))
2989 }
2990
2991 #[wasm_bindgen]
3005 pub fn export_odps_yaml_to_pdf(
3006 odps_yaml: &str,
3007 branding_json: Option<String>,
3008 ) -> Result<String, JsValue> {
3009 use crate::export::pdf::{BrandingConfig, PdfExporter};
3010 use crate::import::ODPSImporter;
3011
3012 let importer = ODPSImporter::new();
3013 let product = importer.import(odps_yaml).map_err(import_error_to_js)?;
3014
3015 let exporter = if let Some(branding_str) = branding_json {
3016 let branding: BrandingConfig =
3017 serde_json::from_str(&branding_str).map_err(deserialization_error)?;
3018 PdfExporter::with_branding(branding)
3019 } else {
3020 PdfExporter::new()
3021 };
3022
3023 let result = exporter
3024 .export_data_product(&product)
3025 .map_err(export_error_to_js)?;
3026 serde_json::to_string(&result).map_err(serialization_error)
3027 }
3028
3029 #[wasm_bindgen]
3041 pub fn export_odps_yaml_to_markdown(odps_yaml: &str) -> Result<String, JsValue> {
3042 use crate::export::pdf::PdfExporter;
3043 use crate::import::ODPSImporter;
3044
3045 let importer = ODPSImporter::new();
3046 let product = importer.import(odps_yaml).map_err(import_error_to_js)?;
3047
3048 let exporter = PdfExporter::new();
3049 Ok(exporter.data_product_to_markdown_public(&product))
3050 }
3051
3052 #[wasm_bindgen]
3066 pub fn export_cads_yaml_to_pdf(
3067 cads_yaml: &str,
3068 branding_json: Option<String>,
3069 ) -> Result<String, JsValue> {
3070 use crate::export::pdf::{BrandingConfig, PdfExporter};
3071 use crate::import::CADSImporter;
3072
3073 let importer = CADSImporter::new();
3074 let asset = importer.import(cads_yaml).map_err(import_error_to_js)?;
3075
3076 let exporter = if let Some(branding_str) = branding_json {
3077 let branding: BrandingConfig =
3078 serde_json::from_str(&branding_str).map_err(deserialization_error)?;
3079 PdfExporter::with_branding(branding)
3080 } else {
3081 PdfExporter::new()
3082 };
3083
3084 let result = exporter
3085 .export_cads_asset(&asset)
3086 .map_err(export_error_to_js)?;
3087 serde_json::to_string(&result).map_err(serialization_error)
3088 }
3089
3090 #[wasm_bindgen]
3102 pub fn export_cads_yaml_to_markdown(cads_yaml: &str) -> Result<String, JsValue> {
3103 use crate::export::pdf::PdfExporter;
3104 use crate::import::CADSImporter;
3105
3106 let importer = CADSImporter::new();
3107 let asset = importer.import(cads_yaml).map_err(import_error_to_js)?;
3108
3109 let exporter = PdfExporter::new();
3110 Ok(exporter.cads_asset_to_markdown_public(&asset))
3111 }
3112
3113 #[wasm_bindgen]
3127 pub fn export_decision_yaml_to_pdf(
3128 decision_yaml: &str,
3129 branding_json: Option<String>,
3130 ) -> Result<String, JsValue> {
3131 use crate::export::pdf::{BrandingConfig, PdfExporter};
3132 use crate::import::decision::DecisionImporter;
3133
3134 let importer = DecisionImporter::new();
3135 let decision = importer
3136 .import_without_validation(decision_yaml)
3137 .map_err(import_error_to_js)?;
3138
3139 let exporter = if let Some(branding_str) = branding_json {
3140 let branding: BrandingConfig =
3141 serde_json::from_str(&branding_str).map_err(deserialization_error)?;
3142 PdfExporter::with_branding(branding)
3143 } else {
3144 PdfExporter::new()
3145 };
3146
3147 let result = exporter
3148 .export_decision(&decision)
3149 .map_err(export_error_to_js)?;
3150 serde_json::to_string(&result).map_err(serialization_error)
3151 }
3152
3153 #[wasm_bindgen]
3165 pub fn export_decision_yaml_to_markdown(decision_yaml: &str) -> Result<String, JsValue> {
3166 use crate::export::markdown::MarkdownExporter;
3167 use crate::import::decision::DecisionImporter;
3168
3169 let importer = DecisionImporter::new();
3170 let decision = importer
3171 .import_without_validation(decision_yaml)
3172 .map_err(import_error_to_js)?;
3173
3174 let exporter = MarkdownExporter::new();
3175 exporter
3176 .export_decision(&decision)
3177 .map_err(export_error_to_js)
3178 }
3179
3180 #[wasm_bindgen]
3194 pub fn export_knowledge_yaml_to_pdf(
3195 knowledge_yaml: &str,
3196 branding_json: Option<String>,
3197 ) -> Result<String, JsValue> {
3198 use crate::export::pdf::{BrandingConfig, PdfExporter};
3199 use crate::import::knowledge::KnowledgeImporter;
3200
3201 let importer = KnowledgeImporter::new();
3202 let article = importer
3203 .import_without_validation(knowledge_yaml)
3204 .map_err(import_error_to_js)?;
3205
3206 let exporter = if let Some(branding_str) = branding_json {
3207 let branding: BrandingConfig =
3208 serde_json::from_str(&branding_str).map_err(deserialization_error)?;
3209 PdfExporter::with_branding(branding)
3210 } else {
3211 PdfExporter::new()
3212 };
3213
3214 let result = exporter
3215 .export_knowledge(&article)
3216 .map_err(export_error_to_js)?;
3217 serde_json::to_string(&result).map_err(serialization_error)
3218 }
3219
3220 #[wasm_bindgen]
3232 pub fn export_knowledge_yaml_to_markdown(knowledge_yaml: &str) -> Result<String, JsValue> {
3233 use crate::export::markdown::MarkdownExporter;
3234 use crate::import::knowledge::KnowledgeImporter;
3235
3236 let importer = KnowledgeImporter::new();
3237 let article = importer
3238 .import_without_validation(knowledge_yaml)
3239 .map_err(import_error_to_js)?;
3240
3241 let exporter = MarkdownExporter::new();
3242 exporter
3243 .export_knowledge(&article)
3244 .map_err(export_error_to_js)
3245 }
3246}