moquilang/
lib.rs

1use wasm_bindgen::prelude::*;
2use serde::{Serialize, Deserialize};
3use std::collections::HashMap;
4use gloo_utils::format::JsValueSerdeExt;
5
6// Import JavaScript functions for entity operations
7#[wasm_bindgen]
8extern "C" {
9    // JavaScript function to create an entity
10    #[wasm_bindgen(js_namespace = entityOps, js_name = createEntity)]
11    fn js_create_entity(entity_name: &str, entity_data: JsValue) -> JsValue;
12    
13    // JavaScript function to update an entity
14    #[wasm_bindgen(js_namespace = entityOps, js_name = updateEntity)]
15    fn js_update_entity(entity_name: &str, entity_data: JsValue) -> JsValue;
16    
17    // JavaScript function to delete an entity
18    #[wasm_bindgen(js_namespace = entityOps, js_name = deleteEntity)]
19    fn js_delete_entity(entity_name: &str, entity_data: JsValue) -> JsValue;
20    
21    // JavaScript function to find one entity
22    #[wasm_bindgen(js_namespace = entityOps, js_name = findOne)]
23    fn js_find_one(entity_name: &str, query: JsValue) -> JsValue;
24    
25    // JavaScript function to find many entities
26    #[wasm_bindgen(js_namespace = entityOps, js_name = findMany)]
27    fn js_find_many(entity_name: &str, query: JsValue) -> JsValue;
28}
29
30// Define a type for service parameters
31#[derive(Serialize, Deserialize)]
32pub struct ServiceParameters {
33    /// The flattened map of parameter names to values
34    #[serde(flatten)]
35    params: HashMap<String, serde_json::Value>,
36}
37
38// Export a function that can call services with arbitrary JSON parameters
39#[wasm_bindgen(js_name = "callService")]
40pub fn call_service(name: &str, parameters: JsValue) -> Result<JsValue, JsValue> {
41    // Validate inputs
42    if name.trim().is_empty() {
43        return Err(JsValue::from_str("Service name cannot be empty"));
44    }
45    
46    // Deserialize the parameters from JavaScript using JSON
47    let params: ServiceParameters = parameters.into_serde()
48        .map_err(|e| JsValue::from_str(&format!("Failed to parse parameters: {}", e)))?;
49    
50    // Here you would implement the actual service call logic
51    // For now, we'll just echo back the service name and parameters
52    
53    let mut result = HashMap::new();
54    result.insert("service".to_string(), name.to_string());
55    result.insert("status".to_string(), "success".to_string());
56    result.insert("parameters".to_string(), format!("{:?}", params.params));
57    
58    // Serialize the result back to JavaScript using JSON
59    JsValue::from_serde(&result)
60        .map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e)))
61}
62
63// Entity operation wrapper functions
64
65/// Create a new entity
66#[wasm_bindgen(js_name = "createEntity")]
67pub fn create_entity(entity_name: &str, entity_data: JsValue) -> Result<JsValue, JsValue> {
68    // Call the JavaScript function
69    let result = js_create_entity(entity_name, entity_data);
70    
71    // Return the result
72    Ok(result)
73}
74
75/// Update an existing entity
76#[wasm_bindgen(js_name = "updateEntity")]
77pub fn update_entity(entity_name: &str, entity_data: JsValue) -> Result<JsValue, JsValue> {
78    // Call the JavaScript function
79    let result = js_update_entity(entity_name, entity_data);
80    
81    // Return the result
82    Ok(result)
83}
84
85/// Delete an entity
86#[wasm_bindgen(js_name = "deleteEntity")]
87pub fn delete_entity(entity_name: &str, entity_data: JsValue) -> Result<JsValue, JsValue> {
88    // Call the JavaScript function
89    let result = js_delete_entity(entity_name, entity_data);
90    
91    // Return the result
92    Ok(result)
93}
94
95/// Find one entity matching the query
96#[wasm_bindgen(js_name = "findOne")]
97pub fn find_one(entity_name: &str, query: JsValue) -> Result<JsValue, JsValue> {
98    // Call the JavaScript function
99    let result = js_find_one(entity_name, query);
100    
101    // Return the result
102    Ok(result)
103}
104
105/// Find multiple entities matching the query
106#[wasm_bindgen(js_name = "findMany")]
107pub fn find_many(entity_name: &str, query: JsValue) -> Result<JsValue, JsValue> {
108    // Call the JavaScript function
109    let result = js_find_many(entity_name, query);
110    
111    // Return the result
112    Ok(result)
113}
114
115/// Schema definition structures for database metadata
116
117/// Information about a database column
118#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
119pub struct ColumnInfo {
120    /// The column name
121    pub name: String,
122    /// The data type (e.g., "TEXT", "INTEGER", "DATETIME")
123    pub data_type: String,
124    /// Whether the column can contain NULL values
125    pub nullable: bool,
126    /// Whether the column is part of the primary key
127    pub primary_key: bool,
128    /// The default value for the column, if any
129    pub default_value: Option<String>,
130    /// A description of the column's purpose
131    pub description: Option<String>,
132}
133
134/// Information about a database table
135#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
136pub struct TableInfo {
137    /// The table name
138    pub name: String,
139    /// A description of the table's purpose
140    pub description: Option<String>,
141    /// The columns in the table
142    pub columns: Vec<ColumnInfo>,
143    /// The names of columns that form the primary key
144    pub primary_key: Vec<String>,
145    /// Foreign key relationships to other tables
146    pub foreign_keys: Vec<ForeignKeyInfo>,
147    /// Indexes defined on the table
148    pub indexes: Vec<IndexInfo>,
149}
150
151/// Information about a foreign key relationship
152#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
153pub struct ForeignKeyInfo {
154    /// The name of the foreign key constraint, if any
155    pub name: Option<String>,
156    /// The columns in this table that form the foreign key
157    pub column_names: Vec<String>,
158    /// The referenced table
159    pub referenced_table: String,
160    /// The columns in the referenced table
161    pub referenced_columns: Vec<String>,
162    /// The action to take on update (e.g., "CASCADE", "SET NULL")
163    pub on_update: Option<String>,
164    /// The action to take on delete (e.g., "CASCADE", "SET NULL")
165    pub on_delete: Option<String>,
166}
167
168/// Information about an index
169#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
170pub struct IndexInfo {
171    /// The index name
172    pub name: String,
173    /// The columns included in the index
174    pub column_names: Vec<String>,
175    /// Whether the index enforces uniqueness
176    pub unique: bool,
177}
178
179/// Complete schema information for a database
180#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
181pub struct DatabaseSchema {
182    /// The tables in the database
183    pub tables: Vec<TableInfo>,
184}
185
186/// Export database schema information as JSON.
187///
188/// This function extracts table definitions and returns them as a JavaScript object.
189/// In a real implementation, this would extract schema from SQLite metadata tables,
190/// but for demonstration purposes it currently returns a mock schema.
191///
192/// # Returns
193///
194/// * `Ok(JsValue)` - The database schema as a JavaScript object
195/// * `Err(JsValue)` - An error message if serialization failed
196///
197/// # Example
198///
199/// ```javascript
200/// // JavaScript
201/// const schema = exportDatabaseSchema();
202/// console.log(`Database has ${schema.tables.length} tables`);
203/// ```
204#[wasm_bindgen(js_name = "exportDatabaseSchema")]
205pub fn export_database_schema() -> Result<JsValue, JsValue> {
206    // Create a mock schema for demonstration
207    // In a real implementation, this would extract schema from SQLite metadata tables
208    let schema = create_mock_schema();
209    
210    // Serialize the schema to JSON
211    JsValue::from_serde(&schema)
212        .map_err(|e| JsValue::from_str(&format!("Failed to serialize schema: {}", e)))
213}
214
215// Helper function to create a mock schema for demonstration
216fn create_mock_schema() -> DatabaseSchema {
217    // UserAccount table based on moqui.security.UserAccount entity
218    let user_account_columns = vec![
219        ColumnInfo {
220            name: "userId".to_string(),
221            data_type: "TEXT".to_string(),
222            nullable: false,
223            primary_key: true,
224            default_value: None,
225            description: Some("Unique identifier for the user".to_string()),
226        },
227        ColumnInfo {
228            name: "username".to_string(),
229            data_type: "TEXT".to_string(),
230            nullable: false,
231            primary_key: false,
232            default_value: None,
233            description: Some("The username used along with the password to login".to_string()),
234        },
235        ColumnInfo {
236            name: "userFullName".to_string(),
237            data_type: "TEXT".to_string(),
238            nullable: true,
239            primary_key: false,
240            default_value: None,
241            description: Some("User's first, middle, last, etc name".to_string()),
242        },
243        ColumnInfo {
244            name: "currentPassword".to_string(),
245            data_type: "TEXT".to_string(),
246            nullable: true,
247            primary_key: false,
248            default_value: None,
249            description: Some("NOTE: not an encrypted field because one way hash encryption used for it".to_string()),
250        },
251        ColumnInfo {
252            name: "resetPassword".to_string(),
253            data_type: "TEXT".to_string(),
254            nullable: true,
255            primary_key: false,
256            default_value: None,
257            description: Some("Set to random password for password reset, can be used only to update password".to_string()),
258        },
259        ColumnInfo {
260            name: "passwordSalt".to_string(),
261            data_type: "TEXT".to_string(),
262            nullable: true,
263            primary_key: false,
264            default_value: None,
265            description: None,
266        },
267        ColumnInfo {
268            name: "passwordHashType".to_string(),
269            data_type: "TEXT".to_string(),
270            nullable: true,
271            primary_key: false,
272            default_value: None,
273            description: None,
274        },
275        ColumnInfo {
276            name: "passwordBase64".to_string(),
277            data_type: "TEXT".to_string(),
278            nullable: true,
279            primary_key: false,
280            default_value: None,
281            description: Some("Set to Y is currentPassword Base64 encoded, defaults to Hex encoded".to_string()),
282        },
283        ColumnInfo {
284            name: "passwordSetDate".to_string(),
285            data_type: "DATETIME".to_string(),
286            nullable: true,
287            primary_key: false,
288            default_value: None,
289            description: None,
290        },
291        ColumnInfo {
292            name: "passwordHint".to_string(),
293            data_type: "TEXT".to_string(),
294            nullable: true,
295            primary_key: false,
296            default_value: None,
297            description: None,
298        },
299        ColumnInfo {
300            name: "publicKey".to_string(),
301            data_type: "TEXT".to_string(),
302            nullable: true,
303            primary_key: false,
304            default_value: None,
305            description: Some("RSA public key for key based authentication".to_string()),
306        },
307        ColumnInfo {
308            name: "hasLoggedOut".to_string(),
309            data_type: "TEXT".to_string(),
310            nullable: true,
311            primary_key: false,
312            default_value: None,
313            description: Some("Set to Y when user logs out and to N when user logs in. If user is session authenticated on request and this is Y then treat as if user not authenticated.".to_string()),
314        },
315        ColumnInfo {
316            name: "disabled".to_string(),
317            data_type: "TEXT".to_string(),
318            nullable: false,
319            primary_key: false,
320            default_value: Some("'N'".to_string()),
321            description: None,
322        },
323        ColumnInfo {
324            name: "disabledDateTime".to_string(),
325            data_type: "DATETIME".to_string(),
326            nullable: true,
327            primary_key: false,
328            default_value: None,
329            description: None,
330        },
331        ColumnInfo {
332            name: "terminateDate".to_string(),
333            data_type: "DATETIME".to_string(),
334            nullable: true,
335            primary_key: false,
336            default_value: None,
337            description: Some("If set then user may not login after this date, and no notifications will be sent after this date.".to_string()),
338        },
339        ColumnInfo {
340            name: "successiveFailedLogins".to_string(),
341            data_type: "INTEGER".to_string(),
342            nullable: true,
343            primary_key: false,
344            default_value: None,
345            description: None,
346        },
347        ColumnInfo {
348            name: "requirePasswordChange".to_string(),
349            data_type: "TEXT".to_string(),
350            nullable: true,
351            primary_key: false,
352            default_value: None,
353            description: None,
354        },
355        ColumnInfo {
356            name: "currencyUomId".to_string(),
357            data_type: "TEXT".to_string(),
358            nullable: true,
359            primary_key: false,
360            default_value: None,
361            description: None,
362        },
363        ColumnInfo {
364            name: "locale".to_string(),
365            data_type: "TEXT".to_string(),
366            nullable: true,
367            primary_key: false,
368            default_value: None,
369            description: None,
370        },
371        ColumnInfo {
372            name: "timeZone".to_string(),
373            data_type: "TEXT".to_string(),
374            nullable: true,
375            primary_key: false,
376            default_value: None,
377            description: None,
378        },
379        ColumnInfo {
380            name: "externalUserId".to_string(),
381            data_type: "TEXT".to_string(),
382            nullable: true,
383            primary_key: false,
384            default_value: None,
385            description: None,
386        },
387        ColumnInfo {
388            name: "emailAddress".to_string(),
389            data_type: "TEXT".to_string(),
390            nullable: true,
391            primary_key: false,
392            default_value: None,
393            description: Some("The email address to use for forgot password emails and other system messages.".to_string()),
394        },
395        ColumnInfo {
396            name: "ipAllowed".to_string(),
397            data_type: "TEXT".to_string(),
398            nullable: true,
399            primary_key: false,
400            default_value: None,
401            description: Some("If specified only allow login from matching IP4 address. Comma separated patterns where each pattern has 4 dot separated segments each segment may be number, '*' for wildcard, or '-' separate number range (like '0-31').".to_string()),
402        },
403    ];
404    
405    let user_account_table = TableInfo {
406        name: "UserAccount".to_string(),
407        description: Some("User account information from moqui.security package".to_string()),
408        columns: user_account_columns,
409        primary_key: vec!["userId".to_string()],
410        foreign_keys: Vec::new(),
411        indexes: vec![
412            IndexInfo {
413                name: "USERACCT_USERNAME".to_string(),
414                column_names: vec!["username".to_string()],
415                unique: true,
416            },
417            IndexInfo {
418                name: "USERACCT_EMAILADDR".to_string(),
419                column_names: vec!["emailAddress".to_string()],
420                unique: true,
421            },
422        ],
423    };
424    
425    DatabaseSchema {
426        tables: vec![user_account_table],
427    }
428}