sz_rust_sdk/core/
engine.rs

1//! Core implementation of SzEngine trait
2#![allow(unused_variables)]
3
4use crate::{
5    error::{SzError, SzResult},
6    ffi_call,
7    flags::SzFlags,
8    traits::SzEngine,
9    types::*,
10};
11use std::collections::HashSet;
12
13/// Core implementation of the SzEngine trait
14pub struct SzEngineCore;
15
16impl SzEngineCore {
17    pub fn new() -> SzResult<Self> {
18        Ok(Self)
19    }
20}
21
22impl SzEngine for SzEngineCore {
23    fn prime_engine(&self) -> SzResult<()> {
24        ffi_call!(crate::ffi::bindings::Sz_primeEngine());
25        Ok(())
26    }
27
28    fn get_stats(&self) -> SzResult<JsonString> {
29        unsafe {
30            let result = crate::ffi::bindings::Sz_stats_helper();
31            crate::ffi::helpers::process_pointer_result(result)
32        }
33    }
34
35    fn add_record(
36        &self,
37        data_source_code: &str,
38        record_id: &str,
39        record_definition: &str,
40        flags: Option<SzFlags>,
41    ) -> SzResult<JsonString> {
42        let data_source_c = crate::ffi::helpers::str_to_c_string(data_source_code)?;
43        let record_id_c = crate::ffi::helpers::str_to_c_string(record_id)?;
44        let record_def_c = crate::ffi::helpers::str_to_c_string(record_definition)?;
45        let flags_bits = flags.unwrap_or(SzFlags::ADD_RECORD_DEFAULT).bits() as i64;
46
47        let result = unsafe {
48            crate::ffi::bindings::Sz_addRecordWithInfo_helper(
49                data_source_c.as_ptr(),
50                record_id_c.as_ptr(),
51                record_def_c.as_ptr(),
52                flags_bits,
53            )
54        };
55
56        unsafe { crate::ffi::helpers::process_engine_pointer_result(result) }
57    }
58
59    fn get_record_preview(
60        &self,
61        record_definition: &str,
62        flags: Option<SzFlags>,
63    ) -> SzResult<JsonString> {
64        let record_def_c = crate::ffi::helpers::str_to_c_string(record_definition)?;
65        let flags_bits = flags.unwrap_or(SzFlags::GET_RECORD_DEFAULT).bits() as i64;
66
67        let result = unsafe {
68            crate::ffi::bindings::Sz_getRecordPreview_helper(record_def_c.as_ptr(), flags_bits)
69        };
70
71        unsafe { crate::ffi::helpers::process_pointer_result(result) }
72    }
73
74    fn delete_record(
75        &self,
76        data_source_code: &str,
77        record_id: &str,
78        flags: Option<SzFlags>,
79    ) -> SzResult<JsonString> {
80        let data_source_c = crate::ffi::helpers::str_to_c_string(data_source_code)?;
81        let record_id_c = crate::ffi::helpers::str_to_c_string(record_id)?;
82        let flags_bits = flags.unwrap_or(SzFlags::DELETE_RECORD_DEFAULT).bits() as i64;
83
84        let result = unsafe {
85            crate::ffi::bindings::Sz_deleteRecordWithInfo_helper(
86                data_source_c.as_ptr(),
87                record_id_c.as_ptr(),
88                flags_bits,
89            )
90        };
91
92        unsafe { crate::ffi::helpers::process_engine_pointer_result(result) }
93    }
94
95    fn reevaluate_record(
96        &self,
97        data_source_code: &str,
98        record_id: &str,
99        flags: Option<SzFlags>,
100    ) -> SzResult<JsonString> {
101        let data_source_c = crate::ffi::helpers::str_to_c_string(data_source_code)?;
102        let record_id_c = crate::ffi::helpers::str_to_c_string(record_id)?;
103        let flags_bits = flags.unwrap_or(SzFlags::REEVALUATE_RECORD_DEFAULT).bits() as i64;
104
105        let result = unsafe {
106            crate::ffi::bindings::Sz_reevaluateRecordWithInfo_helper(
107                data_source_c.as_ptr(),
108                record_id_c.as_ptr(),
109                flags_bits,
110            )
111        };
112
113        unsafe { crate::ffi::helpers::process_pointer_result(result) }
114    }
115
116    fn reevaluate_entity(
117        &self,
118        entity_id: EntityId,
119        flags: Option<SzFlags>,
120    ) -> SzResult<JsonString> {
121        let flags_value =
122            flags.unwrap_or(SzFlags::REEVALUATE_ENTITY_DEFAULT).bits() as libc::c_longlong;
123
124        let result = unsafe {
125            crate::ffi::bindings::Sz_reevaluateEntityWithInfo_helper(entity_id, flags_value)
126        };
127
128        unsafe { crate::ffi::helpers::process_pointer_result(result) }
129    }
130
131    fn search_by_attributes(
132        &self,
133        attributes: &str,
134        search_profile: Option<&str>,
135        flags: Option<SzFlags>,
136    ) -> SzResult<JsonString> {
137        let attributes_c = crate::ffi::helpers::str_to_c_string(attributes)?;
138        let flags_bits = flags
139            .unwrap_or(SzFlags::SEARCH_BY_ATTRIBUTES_DEFAULT)
140            .bits() as i64;
141
142        let result = if let Some(profile) = search_profile {
143            let search_profile_c = crate::ffi::helpers::str_to_c_string(profile)?;
144            unsafe {
145                crate::ffi::bindings::Sz_searchByAttributes_V3_helper(
146                    attributes_c.as_ptr(),
147                    search_profile_c.as_ptr(),
148                    flags_bits,
149                )
150            }
151        } else {
152            unsafe { crate::ffi::bindings::Sz_searchByAttributes_helper(attributes_c.as_ptr()) }
153        };
154
155        unsafe { crate::ffi::helpers::process_engine_pointer_result(result) }
156    }
157
158    fn why_search(
159        &self,
160        attributes: &str,
161        entity_id: EntityId,
162        search_profile: Option<&str>,
163        flags: Option<SzFlags>,
164    ) -> SzResult<JsonString> {
165        let attributes_c = crate::ffi::helpers::str_to_c_string(attributes)?;
166        let search_profile_c = search_profile
167            .map(crate::ffi::helpers::str_to_c_string)
168            .transpose()?;
169        let search_profile_ptr = search_profile_c
170            .as_ref()
171            .map(|c_str| c_str.as_ptr())
172            .unwrap_or(std::ptr::null());
173        let flags_bits = flags.unwrap_or(SzFlags::WHY_SEARCH_DEFAULT).bits() as i64;
174
175        let result = unsafe {
176            crate::ffi::bindings::Sz_whySearch_helper(
177                attributes_c.as_ptr(),
178                entity_id,
179                search_profile_ptr,
180                flags_bits,
181            )
182        };
183
184        unsafe { crate::ffi::helpers::process_pointer_result(result) }
185    }
186
187    fn get_entity(&self, entity_id: EntityId, flags: Option<SzFlags>) -> SzResult<JsonString> {
188        let result = unsafe { crate::ffi::bindings::Sz_getEntityByEntityID_helper(entity_id) };
189
190        unsafe { crate::ffi::helpers::process_engine_pointer_result(result) }
191    }
192
193    fn get_entity_by_record(
194        &self,
195        data_source_code: &str,
196        record_id: &str,
197        flags: Option<SzFlags>,
198    ) -> SzResult<JsonString> {
199        let data_source_c = crate::ffi::helpers::str_to_c_string(data_source_code)?;
200        let record_id_c = crate::ffi::helpers::str_to_c_string(record_id)?;
201        let flags_bits = flags.unwrap_or(SzFlags::GET_ENTITY_DEFAULT).bits() as i64;
202
203        let result = unsafe {
204            crate::ffi::bindings::Sz_getEntityByRecordID_helper(
205                data_source_c.as_ptr(),
206                record_id_c.as_ptr(),
207            )
208        };
209
210        unsafe { crate::ffi::helpers::process_engine_pointer_result(result) }
211    }
212
213    fn get_record(
214        &self,
215        data_source_code: &str,
216        record_id: &str,
217        flags: Option<SzFlags>,
218    ) -> SzResult<JsonString> {
219        let data_source_c = crate::ffi::helpers::str_to_c_string(data_source_code)?;
220        let record_id_c = crate::ffi::helpers::str_to_c_string(record_id)?;
221        let flags_value = flags.map(|f| f.bits() as i64).unwrap_or(0);
222
223        let result = unsafe {
224            crate::ffi::bindings::Sz_getRecord_helper(
225                data_source_c.as_ptr(),
226                record_id_c.as_ptr(),
227                flags_value,
228            )
229        };
230
231        if result.return_code != 0 {
232            crate::ffi::helpers::check_return_code(result.return_code)?;
233        }
234
235        unsafe { crate::ffi::helpers::c_str_to_string(result.response) }
236    }
237
238    fn find_interesting_entities_by_entity_id(
239        &self,
240        entity_id: EntityId,
241        flags: Option<SzFlags>,
242    ) -> SzResult<JsonString> {
243        let flags_bits = flags
244            .unwrap_or(SzFlags::FIND_INTERESTING_ENTITIES_DEFAULT)
245            .bits() as i64;
246
247        let result = unsafe {
248            crate::ffi::bindings::Sz_findInterestingEntitiesByEntityID_helper(entity_id, flags_bits)
249        };
250
251        unsafe { crate::ffi::helpers::process_pointer_result(result) }
252    }
253
254    fn find_interesting_entities_by_record(
255        &self,
256        data_source_code: &str,
257        record_id: &str,
258        flags: Option<SzFlags>,
259    ) -> SzResult<JsonString> {
260        let data_source_c = crate::ffi::helpers::str_to_c_string(data_source_code)?;
261        let record_id_c = crate::ffi::helpers::str_to_c_string(record_id)?;
262        let flags_bits = flags
263            .unwrap_or(SzFlags::FIND_INTERESTING_ENTITIES_DEFAULT)
264            .bits() as i64;
265
266        let result = unsafe {
267            crate::ffi::bindings::Sz_findInterestingEntitiesByRecordID_helper(
268                data_source_c.as_ptr(),
269                record_id_c.as_ptr(),
270                flags_bits,
271            )
272        };
273
274        unsafe { crate::ffi::helpers::process_pointer_result(result) }
275    }
276
277    fn find_path(
278        &self,
279        start_entity_id: EntityId,
280        end_entity_id: EntityId,
281        max_degrees: i64,
282        avoid_entity_ids: Option<&HashSet<EntityId>>,
283        required_data_sources: Option<&HashSet<String>>,
284        flags: Option<SzFlags>,
285    ) -> SzResult<JsonString> {
286        let flags_bits = flags.unwrap_or(SzFlags::FIND_PATH_DEFAULT).bits() as i64;
287
288        let result_ptr = unsafe {
289            crate::ffi::bindings::Sz_findPathByEntityID_helper(
290                start_entity_id,
291                end_entity_id,
292                max_degrees,
293                flags_bits,
294            )
295        };
296
297        if result_ptr.is_null() {
298            return Err(SzError::unknown("Failed to find path"));
299        }
300
301        unsafe { crate::ffi::helpers::c_str_to_string(result_ptr) }
302    }
303
304    fn find_network(
305        &self,
306        entity_list: &[EntityId],
307        max_degrees: i64,
308        build_out_degree: i64,
309        max_entities: i64,
310        flags: Option<SzFlags>,
311    ) -> SzResult<JsonString> {
312        // Format entity list as JSON object with ENTITIES array, as expected by Senzing
313        let entity_objects: Vec<serde_json::Value> = entity_list
314            .iter()
315            .map(|&id| serde_json::json!({"ENTITY_ID": id}))
316            .collect();
317
318        let entity_list_json = serde_json::json!({
319            "ENTITIES": entity_objects
320        })
321        .to_string();
322
323        let entity_list_c = crate::ffi::helpers::str_to_c_string(&entity_list_json)?;
324        let flags_bits = flags.unwrap_or(SzFlags::FIND_NETWORK_DEFAULT).bits() as i64;
325
326        let result = unsafe {
327            crate::ffi::bindings::Sz_findNetworkByEntityID_V2_helper(
328                entity_list_c.as_ptr(),
329                max_degrees,
330                build_out_degree,
331                max_entities,
332                flags_bits,
333            )
334        };
335
336        // Process the network-specific result structure
337        if result.return_code != 0 {
338            crate::ffi::helpers::check_return_code(result.return_code)?;
339        }
340
341        unsafe { crate::ffi::helpers::c_str_to_string(result.response) }
342    }
343
344    fn why_entity(
345        &self,
346        entity_id1: EntityId,
347        entity_id2: EntityId,
348        flags: Option<SzFlags>,
349    ) -> SzResult<JsonString> {
350        let flags_value = flags.unwrap_or(SzFlags::WHY_ENTITY_DEFAULT).bits() as libc::c_longlong;
351
352        let result = unsafe {
353            crate::ffi::bindings::Sz_whyEntities_helper(entity_id1, entity_id2, flags_value)
354        };
355
356        unsafe { crate::ffi::helpers::process_pointer_result(result) }
357    }
358
359    fn why_records(
360        &self,
361        data_source_code1: &str,
362        record_id1: &str,
363        data_source_code2: &str,
364        record_id2: &str,
365        flags: Option<SzFlags>,
366    ) -> SzResult<JsonString> {
367        let data_source1_c = crate::ffi::helpers::str_to_c_string(data_source_code1)?;
368        let record_id1_c = crate::ffi::helpers::str_to_c_string(record_id1)?;
369        let data_source2_c = crate::ffi::helpers::str_to_c_string(data_source_code2)?;
370        let record_id2_c = crate::ffi::helpers::str_to_c_string(record_id2)?;
371        let flags_bits = flags.unwrap_or(SzFlags::WHY_RECORD_DEFAULT).bits() as i64;
372
373        let result = unsafe {
374            crate::ffi::bindings::Sz_whyRecords_helper(
375                data_source1_c.as_ptr(),
376                record_id1_c.as_ptr(),
377                data_source2_c.as_ptr(),
378                record_id2_c.as_ptr(),
379                flags_bits,
380            )
381        };
382
383        unsafe { crate::ffi::helpers::process_pointer_result(result) }
384    }
385
386    fn why_record_in_entity(
387        &self,
388        data_source_code: &str,
389        record_id: &str,
390        flags: Option<SzFlags>,
391    ) -> SzResult<JsonString> {
392        let data_source_c = crate::ffi::helpers::str_to_c_string(data_source_code)?;
393        let record_id_c = crate::ffi::helpers::str_to_c_string(record_id)?;
394        let flags_bits = flags.unwrap_or(SzFlags::WHY_RECORD_DEFAULT).bits() as i64;
395
396        let result = unsafe {
397            crate::ffi::bindings::Sz_whyRecordInEntity_helper(
398                data_source_c.as_ptr(),
399                record_id_c.as_ptr(),
400                flags_bits,
401            )
402        };
403
404        unsafe { crate::ffi::helpers::process_pointer_result(result) }
405    }
406
407    fn how_entity(&self, entity_id: EntityId, flags: Option<SzFlags>) -> SzResult<JsonString> {
408        let flags_bits = flags.unwrap_or(SzFlags::HOW_ENTITY_DEFAULT).bits() as i64;
409
410        let result =
411            unsafe { crate::ffi::bindings::Sz_howEntityByEntityID_helper(entity_id, flags_bits) };
412
413        unsafe { crate::ffi::helpers::process_pointer_result(result) }
414    }
415
416    fn get_virtual_entity(
417        &self,
418        record_keys: &[(String, String)],
419        flags: Option<SzFlags>,
420    ) -> SzResult<JsonString> {
421        // The C# SDK expects ISet<(string dataSourceCode, string recordID)> recordKeys
422        // We need to iterate through the record keys and call getVirtualEntityByRecordID for each
423        // However, the native library only has Sz_getVirtualEntityByRecordID for single records
424        // This method might need to aggregate results or use a different approach
425        if record_keys.is_empty() {
426            return Err(SzError::configuration("No record keys provided"));
427        }
428
429        // For now, if only one record key is provided, use the native function
430        if record_keys.len() == 1 {
431            let (data_source, record_id) = &record_keys[0];
432            let data_source_c = crate::ffi::helpers::str_to_c_string(data_source)?;
433            let record_id_c = crate::ffi::helpers::str_to_c_string(record_id)?;
434
435            let result = unsafe {
436                crate::ffi::bindings::Sz_getVirtualEntityByRecordID_helper(
437                    data_source_c.as_ptr(),
438                    record_id_c.as_ptr(),
439                )
440            };
441
442            unsafe { crate::ffi::helpers::process_pointer_result(result) }
443        } else {
444            // Multiple record keys - this needs special handling
445            Err(SzError::configuration(
446                "Multiple record keys for get_virtual_entity not yet supported. Native library only supports single record.",
447            ))
448        }
449    }
450
451    fn process_redo_record(
452        &self,
453        redo_record: &str,
454        flags: Option<SzFlags>,
455    ) -> SzResult<JsonString> {
456        let redo_record_c = crate::ffi::helpers::str_to_c_string(redo_record)?;
457        let flags_bits = flags.unwrap_or_default().bits() as i64;
458
459        let result = unsafe {
460            crate::ffi::bindings::Sz_processRedoRecordWithInfo_helper(
461                redo_record_c.as_ptr(),
462                flags_bits,
463            )
464        };
465
466        unsafe { crate::ffi::helpers::process_pointer_result(result) }
467    }
468
469    fn get_redo_record(&self) -> SzResult<JsonString> {
470        let result = unsafe { crate::ffi::bindings::Sz_getRedoRecord_helper() };
471        unsafe { crate::ffi::helpers::process_pointer_result(result) }
472    }
473
474    fn count_redo_records(&self) -> SzResult<i64> {
475        let count = unsafe { crate::ffi::bindings::Sz_countRedoRecords() };
476        Ok(count)
477    }
478
479    fn export_json_entity_report(&self, flags: Option<SzFlags>) -> SzResult<ExportHandle> {
480        let flags_bits = flags.unwrap_or_default().bits() as i64;
481
482        let result = unsafe { crate::ffi::bindings::Sz_exportJSONEntityReport_helper(flags_bits) };
483
484        let export_handle_str =
485            unsafe { crate::ffi::helpers::process_engine_pointer_result(result) }?;
486
487        // Convert the string handle to an i64 for our API
488        export_handle_str
489            .parse()
490            .map_err(|_| SzError::ffi("Invalid export handle"))
491    }
492
493    fn export_csv_entity_report(
494        &self,
495        csv_column_list: &str,
496        flags: Option<SzFlags>,
497    ) -> SzResult<ExportHandle> {
498        let csv_columns_c = crate::ffi::helpers::str_to_c_string(csv_column_list)?;
499        let flags_bits = flags.unwrap_or_default().bits() as i64;
500
501        let result = unsafe {
502            crate::ffi::bindings::Sz_exportCSVEntityReport_helper(
503                csv_columns_c.as_ptr(),
504                flags_bits,
505            )
506        };
507
508        let export_handle_str =
509            unsafe { crate::ffi::helpers::process_engine_pointer_result(result) }?;
510
511        // Convert the string handle to an i64 for our API
512        export_handle_str
513            .parse()
514            .map_err(|_| SzError::ffi("Invalid export handle"))
515    }
516
517    fn fetch_next(&self, export_handle: ExportHandle) -> SzResult<JsonString> {
518        let handle_str = export_handle.to_string();
519        let handle_c = crate::ffi::helpers::str_to_c_string(&handle_str)?;
520
521        let result = unsafe { crate::ffi::bindings::Sz_fetchNext_helper(handle_c.as_ptr()) };
522
523        unsafe { crate::ffi::helpers::process_engine_pointer_result(result) }
524    }
525
526    fn close_export(&self, export_handle: ExportHandle) -> SzResult<()> {
527        let handle_str = export_handle.to_string();
528        let handle_c = crate::ffi::helpers::str_to_c_string(&handle_str)?;
529
530        ffi_call!(crate::ffi::bindings::Sz_closeExportReport_helper(
531            handle_c.as_ptr()
532        ));
533        Ok(())
534    }
535}
536
537// Note: SzEngineCore no longer needs Drop implementation
538// since it doesn't manage any resources directly