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 });
1724 workspace.last_modified_at = Utc::now();
1725
1726 serde_json::to_string(&workspace).map_err(serialization_error)
1727 }
1728
1729 #[wasm_bindgen]
1740 pub fn remove_domain_from_workspace(
1741 workspace_json: &str,
1742 domain_id: &str,
1743 ) -> Result<String, JsValue> {
1744 use crate::models::workspace::Workspace;
1745 use chrono::Utc;
1746 use uuid::Uuid;
1747
1748 let mut workspace: Workspace =
1749 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1750 let domain_uuid =
1751 Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
1752
1753 let original_len = workspace.domains.len();
1754 workspace.domains.retain(|d| d.id != domain_uuid);
1755
1756 if workspace.domains.len() == original_len {
1757 return Err(WasmError::new(
1758 "NotFoundError",
1759 format!("Domain {} not found in workspace", domain_id),
1760 )
1761 .with_code("DOMAIN_NOT_FOUND")
1762 .to_js_value());
1763 }
1764
1765 workspace.last_modified_at = Utc::now();
1766 serde_json::to_string(&workspace).map_err(serialization_error)
1767 }
1768
1769 #[wasm_bindgen]
1780 pub fn add_relationship_to_workspace(
1781 workspace_json: &str,
1782 relationship_json: &str,
1783 ) -> Result<String, JsValue> {
1784 use crate::models::Relationship;
1785 use crate::models::workspace::Workspace;
1786
1787 let mut workspace: Workspace =
1788 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1789 let relationship: Relationship =
1790 serde_json::from_str(relationship_json).map_err(deserialization_error)?;
1791
1792 if workspace
1794 .relationships
1795 .iter()
1796 .any(|r| r.id == relationship.id)
1797 {
1798 return Err(WasmError::new(
1799 "DuplicateError",
1800 format!(
1801 "Relationship {} already exists in workspace",
1802 relationship.id
1803 ),
1804 )
1805 .with_code("DUPLICATE_RELATIONSHIP")
1806 .to_js_value());
1807 }
1808
1809 workspace.add_relationship(relationship);
1810 serde_json::to_string(&workspace).map_err(serialization_error)
1811 }
1812
1813 #[wasm_bindgen]
1824 pub fn remove_relationship_from_workspace(
1825 workspace_json: &str,
1826 relationship_id: &str,
1827 ) -> Result<String, JsValue> {
1828 use crate::models::workspace::Workspace;
1829 use uuid::Uuid;
1830
1831 let mut workspace: Workspace =
1832 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1833 let relationship_uuid = Uuid::parse_str(relationship_id)
1834 .map_err(|e| invalid_input_error("relationship ID", e))?;
1835
1836 if !workspace.remove_relationship(relationship_uuid) {
1837 return Err(WasmError::new(
1838 "NotFoundError",
1839 format!("Relationship {} not found in workspace", relationship_id),
1840 )
1841 .with_code("RELATIONSHIP_NOT_FOUND")
1842 .to_js_value());
1843 }
1844
1845 serde_json::to_string(&workspace).map_err(serialization_error)
1846 }
1847
1848 #[wasm_bindgen]
1859 pub fn get_workspace_relationships_for_source(
1860 workspace_json: &str,
1861 source_table_id: &str,
1862 ) -> Result<String, JsValue> {
1863 use crate::models::workspace::Workspace;
1864 use uuid::Uuid;
1865
1866 let workspace: Workspace =
1867 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1868 let source_uuid = Uuid::parse_str(source_table_id)
1869 .map_err(|e| invalid_input_error("source table ID", e))?;
1870
1871 let relationships: Vec<_> = workspace.get_relationships_for_source(source_uuid);
1872 serde_json::to_string(&relationships).map_err(serialization_error)
1873 }
1874
1875 #[wasm_bindgen]
1886 pub fn get_workspace_relationships_for_target(
1887 workspace_json: &str,
1888 target_table_id: &str,
1889 ) -> Result<String, JsValue> {
1890 use crate::models::workspace::Workspace;
1891 use uuid::Uuid;
1892
1893 let workspace: Workspace =
1894 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1895 let target_uuid = Uuid::parse_str(target_table_id)
1896 .map_err(|e| invalid_input_error("target table ID", e))?;
1897
1898 let relationships: Vec<_> = workspace.get_relationships_for_target(target_uuid);
1899 serde_json::to_string(&relationships).map_err(serialization_error)
1900 }
1901
1902 #[wasm_bindgen]
1913 pub fn create_domain_config(name: &str, workspace_id: &str) -> Result<String, JsValue> {
1914 use crate::models::domain_config::DomainConfig;
1915 use chrono::Utc;
1916 use std::collections::HashMap;
1917 use uuid::Uuid;
1918
1919 let workspace_uuid =
1920 Uuid::parse_str(workspace_id).map_err(|e| invalid_input_error("workspace ID", e))?;
1921
1922 let config = DomainConfig {
1923 id: Uuid::new_v4(),
1924 workspace_id: workspace_uuid,
1925 name: name.to_string(),
1926 description: None,
1927 created_at: Utc::now(),
1928 last_modified_at: Utc::now(),
1929 owner: None,
1930 systems: Vec::new(),
1931 tables: Vec::new(),
1932 products: Vec::new(),
1933 assets: Vec::new(),
1934 processes: Vec::new(),
1935 decisions: Vec::new(),
1936 view_positions: HashMap::new(),
1937 folder_path: None,
1938 workspace_path: None,
1939 };
1940
1941 serde_json::to_string(&config).map_err(serialization_error)
1942 }
1943
1944 #[wasm_bindgen]
1954 pub fn parse_domain_config_yaml(yaml_content: &str) -> Result<String, JsValue> {
1955 use crate::models::domain_config::DomainConfig;
1956
1957 let config: DomainConfig = serde_yaml::from_str(yaml_content).map_err(parse_error)?;
1958 serde_json::to_string(&config).map_err(serialization_error)
1959 }
1960
1961 #[wasm_bindgen]
1971 pub fn export_domain_config_to_yaml(config_json: &str) -> Result<String, JsValue> {
1972 use crate::models::domain_config::DomainConfig;
1973
1974 let config: DomainConfig =
1975 serde_json::from_str(config_json).map_err(deserialization_error)?;
1976 serde_yaml::to_string(&config).map_err(serialization_error)
1977 }
1978
1979 #[wasm_bindgen]
1989 pub fn get_domain_config_id(config_json: &str) -> Result<String, JsValue> {
1990 use crate::models::domain_config::DomainConfig;
1991
1992 let config: DomainConfig =
1993 serde_json::from_str(config_json).map_err(deserialization_error)?;
1994 Ok(config.id.to_string())
1995 }
1996
1997 #[wasm_bindgen]
2008 pub fn update_domain_view_positions(
2009 config_json: &str,
2010 positions_json: &str,
2011 ) -> Result<String, JsValue> {
2012 use crate::models::domain_config::{DomainConfig, ViewPosition};
2013 use chrono::Utc;
2014 use std::collections::HashMap;
2015
2016 let mut config: DomainConfig =
2017 serde_json::from_str(config_json).map_err(deserialization_error)?;
2018 let positions: HashMap<String, HashMap<String, ViewPosition>> =
2019 serde_json::from_str(positions_json).map_err(deserialization_error)?;
2020
2021 config.view_positions = positions;
2022 config.last_modified_at = Utc::now();
2023
2024 serde_json::to_string(&config).map_err(serialization_error)
2025 }
2026
2027 #[wasm_bindgen]
2039 pub fn add_entity_to_domain_config(
2040 config_json: &str,
2041 entity_type: &str,
2042 entity_id: &str,
2043 ) -> Result<String, JsValue> {
2044 use crate::models::domain_config::DomainConfig;
2045 use chrono::Utc;
2046 use uuid::Uuid;
2047
2048 let mut config: DomainConfig =
2049 serde_json::from_str(config_json).map_err(deserialization_error)?;
2050 let entity_uuid =
2051 Uuid::parse_str(entity_id).map_err(|e| invalid_input_error("entity ID", e))?;
2052
2053 let entities = match entity_type {
2054 "system" => &mut config.systems,
2055 "table" => &mut config.tables,
2056 "product" => &mut config.products,
2057 "asset" => &mut config.assets,
2058 "process" => &mut config.processes,
2059 "decision" => &mut config.decisions,
2060 _ => {
2061 return Err(invalid_input_error(
2062 "entity type",
2063 "Use 'system', 'table', 'product', 'asset', 'process', or 'decision'",
2064 ));
2065 }
2066 };
2067
2068 if entities.contains(&entity_uuid) {
2069 return Err(WasmError::new(
2070 "DuplicateError",
2071 format!(
2072 "{} {} already exists in domain config",
2073 entity_type, entity_id
2074 ),
2075 )
2076 .with_code("DUPLICATE_ENTITY")
2077 .to_js_value());
2078 }
2079
2080 entities.push(entity_uuid);
2081 config.last_modified_at = Utc::now();
2082
2083 serde_json::to_string(&config).map_err(serialization_error)
2084 }
2085
2086 #[wasm_bindgen]
2098 pub fn remove_entity_from_domain_config(
2099 config_json: &str,
2100 entity_type: &str,
2101 entity_id: &str,
2102 ) -> Result<String, JsValue> {
2103 use crate::models::domain_config::DomainConfig;
2104 use chrono::Utc;
2105 use uuid::Uuid;
2106
2107 let mut config: DomainConfig =
2108 serde_json::from_str(config_json).map_err(deserialization_error)?;
2109 let entity_uuid =
2110 Uuid::parse_str(entity_id).map_err(|e| invalid_input_error("entity ID", e))?;
2111
2112 let entities = match entity_type {
2113 "system" => &mut config.systems,
2114 "table" => &mut config.tables,
2115 "product" => &mut config.products,
2116 "asset" => &mut config.assets,
2117 "process" => &mut config.processes,
2118 "decision" => &mut config.decisions,
2119 _ => {
2120 return Err(invalid_input_error(
2121 "entity type",
2122 "Use 'system', 'table', 'product', 'asset', 'process', or 'decision'",
2123 ));
2124 }
2125 };
2126
2127 let original_len = entities.len();
2128 entities.retain(|id| *id != entity_uuid);
2129
2130 if entities.len() == original_len {
2131 return Err(WasmError::new(
2132 "NotFoundError",
2133 format!("{} {} not found in domain config", entity_type, entity_id),
2134 )
2135 .with_code("ENTITY_NOT_FOUND")
2136 .to_js_value());
2137 }
2138
2139 config.last_modified_at = Utc::now();
2140 serde_json::to_string(&config).map_err(serialization_error)
2141 }
2142
2143 #[wasm_bindgen]
2157 pub fn parse_decision_yaml(yaml_content: &str) -> Result<String, JsValue> {
2158 use crate::import::decision::DecisionImporter;
2159
2160 let importer = DecisionImporter::new();
2161 match importer.import(yaml_content) {
2162 Ok(decision) => serde_json::to_string(&decision).map_err(serialization_error),
2163 Err(e) => Err(import_error_to_js(e)),
2164 }
2165 }
2166
2167 #[wasm_bindgen]
2177 pub fn parse_decision_index_yaml(yaml_content: &str) -> Result<String, JsValue> {
2178 use crate::import::decision::DecisionImporter;
2179
2180 let importer = DecisionImporter::new();
2181 match importer.import_index(yaml_content) {
2182 Ok(index) => serde_json::to_string(&index).map_err(serialization_error),
2183 Err(e) => Err(import_error_to_js(e)),
2184 }
2185 }
2186
2187 #[wasm_bindgen]
2197 pub fn export_decision_to_yaml(decision_json: &str) -> Result<String, JsValue> {
2198 use crate::export::decision::DecisionExporter;
2199 use crate::models::decision::Decision;
2200
2201 let decision: Decision =
2202 serde_json::from_str(decision_json).map_err(deserialization_error)?;
2203 let exporter = DecisionExporter::new();
2204 exporter
2205 .export_without_validation(&decision)
2206 .map_err(export_error_to_js)
2207 }
2208
2209 #[wasm_bindgen]
2219 pub fn export_decision_index_to_yaml(index_json: &str) -> Result<String, JsValue> {
2220 use crate::export::decision::DecisionExporter;
2221 use crate::models::decision::DecisionIndex;
2222
2223 let index: DecisionIndex =
2224 serde_json::from_str(index_json).map_err(deserialization_error)?;
2225 let exporter = DecisionExporter::new();
2226 exporter.export_index(&index).map_err(export_error_to_js)
2227 }
2228
2229 #[wasm_bindgen]
2239 pub fn export_decision_to_markdown(decision_json: &str) -> Result<String, JsValue> {
2240 use crate::export::markdown::MarkdownExporter;
2241 use crate::models::decision::Decision;
2242
2243 let decision: Decision =
2244 serde_json::from_str(decision_json).map_err(deserialization_error)?;
2245 let exporter = MarkdownExporter::new();
2246 exporter
2247 .export_decision(&decision)
2248 .map_err(export_error_to_js)
2249 }
2250
2251 #[wasm_bindgen]
2264 pub fn create_decision(
2265 number: u32,
2266 title: &str,
2267 context: &str,
2268 decision: &str,
2269 ) -> Result<String, JsValue> {
2270 use crate::models::decision::Decision;
2271
2272 let dec = Decision::new(number, title, context, decision);
2273 serde_json::to_string(&dec).map_err(serialization_error)
2274 }
2275
2276 #[wasm_bindgen]
2282 pub fn create_decision_index() -> Result<String, JsValue> {
2283 use crate::models::decision::DecisionIndex;
2284
2285 let index = DecisionIndex::new();
2286 serde_json::to_string(&index).map_err(serialization_error)
2287 }
2288
2289 #[wasm_bindgen]
2301 pub fn add_decision_to_index(
2302 index_json: &str,
2303 decision_json: &str,
2304 filename: &str,
2305 ) -> Result<String, JsValue> {
2306 use crate::models::decision::{Decision, DecisionIndex};
2307
2308 let mut index: DecisionIndex =
2309 serde_json::from_str(index_json).map_err(deserialization_error)?;
2310 let decision: Decision =
2311 serde_json::from_str(decision_json).map_err(deserialization_error)?;
2312
2313 index.add_decision(&decision, filename.to_string());
2314 serde_json::to_string(&index).map_err(serialization_error)
2315 }
2316
2317 #[wasm_bindgen]
2331 pub fn parse_knowledge_yaml(yaml_content: &str) -> Result<String, JsValue> {
2332 use crate::import::knowledge::KnowledgeImporter;
2333
2334 let importer = KnowledgeImporter::new();
2335 match importer.import(yaml_content) {
2336 Ok(article) => serde_json::to_string(&article).map_err(serialization_error),
2337 Err(e) => Err(import_error_to_js(e)),
2338 }
2339 }
2340
2341 #[wasm_bindgen]
2351 pub fn parse_knowledge_index_yaml(yaml_content: &str) -> Result<String, JsValue> {
2352 use crate::import::knowledge::KnowledgeImporter;
2353
2354 let importer = KnowledgeImporter::new();
2355 match importer.import_index(yaml_content) {
2356 Ok(index) => serde_json::to_string(&index).map_err(serialization_error),
2357 Err(e) => Err(import_error_to_js(e)),
2358 }
2359 }
2360
2361 #[wasm_bindgen]
2371 pub fn export_knowledge_to_yaml(article_json: &str) -> Result<String, JsValue> {
2372 use crate::export::knowledge::KnowledgeExporter;
2373 use crate::models::knowledge::KnowledgeArticle;
2374
2375 let article: KnowledgeArticle =
2376 serde_json::from_str(article_json).map_err(deserialization_error)?;
2377 let exporter = KnowledgeExporter::new();
2378 exporter
2379 .export_without_validation(&article)
2380 .map_err(export_error_to_js)
2381 }
2382
2383 #[wasm_bindgen]
2393 pub fn export_knowledge_index_to_yaml(index_json: &str) -> Result<String, JsValue> {
2394 use crate::export::knowledge::KnowledgeExporter;
2395 use crate::models::knowledge::KnowledgeIndex;
2396
2397 let index: KnowledgeIndex =
2398 serde_json::from_str(index_json).map_err(deserialization_error)?;
2399 let exporter = KnowledgeExporter::new();
2400 exporter.export_index(&index).map_err(export_error_to_js)
2401 }
2402
2403 #[wasm_bindgen]
2413 pub fn export_knowledge_to_markdown(article_json: &str) -> Result<String, JsValue> {
2414 use crate::export::markdown::MarkdownExporter;
2415 use crate::models::knowledge::KnowledgeArticle;
2416
2417 let article: KnowledgeArticle =
2418 serde_json::from_str(article_json).map_err(deserialization_error)?;
2419 let exporter = MarkdownExporter::new();
2420 exporter
2421 .export_knowledge(&article)
2422 .map_err(export_error_to_js)
2423 }
2424
2425 #[wasm_bindgen]
2439 pub fn create_knowledge_article(
2440 number: u32,
2441 title: &str,
2442 summary: &str,
2443 content: &str,
2444 author: &str,
2445 ) -> Result<String, JsValue> {
2446 use crate::models::knowledge::KnowledgeArticle;
2447
2448 let article = KnowledgeArticle::new(number, title, summary, content, author);
2449 serde_json::to_string(&article).map_err(serialization_error)
2450 }
2451
2452 #[wasm_bindgen]
2458 pub fn create_knowledge_index() -> Result<String, JsValue> {
2459 use crate::models::knowledge::KnowledgeIndex;
2460
2461 let index = KnowledgeIndex::new();
2462 serde_json::to_string(&index).map_err(serialization_error)
2463 }
2464
2465 #[wasm_bindgen]
2477 pub fn add_article_to_knowledge_index(
2478 index_json: &str,
2479 article_json: &str,
2480 filename: &str,
2481 ) -> Result<String, JsValue> {
2482 use crate::models::knowledge::{KnowledgeArticle, KnowledgeIndex};
2483
2484 let mut index: KnowledgeIndex =
2485 serde_json::from_str(index_json).map_err(deserialization_error)?;
2486 let article: KnowledgeArticle =
2487 serde_json::from_str(article_json).map_err(deserialization_error)?;
2488
2489 index.add_article(&article, filename.to_string());
2490 serde_json::to_string(&index).map_err(serialization_error)
2491 }
2492
2493 #[wasm_bindgen]
2504 pub fn search_knowledge_articles(articles_json: &str, query: &str) -> Result<String, JsValue> {
2505 use crate::models::knowledge::KnowledgeArticle;
2506
2507 let articles: Vec<KnowledgeArticle> =
2508 serde_json::from_str(articles_json).map_err(deserialization_error)?;
2509
2510 let query_lower = query.to_lowercase();
2511 let matches: Vec<&KnowledgeArticle> = articles
2512 .iter()
2513 .filter(|article| {
2514 article.title.to_lowercase().contains(&query_lower)
2515 || article.summary.to_lowercase().contains(&query_lower)
2516 || article.content.to_lowercase().contains(&query_lower)
2517 || article
2518 .tags
2519 .iter()
2520 .any(|tag| tag.to_string().to_lowercase().contains(&query_lower))
2521 })
2522 .collect();
2523
2524 serde_json::to_string(&matches).map_err(serialization_error)
2525 }
2526}