Skip to main content

sz_configtool_lib/functions/
expression.rs

1//! Expression function operations for Senzing configuration
2//!
3//! This module provides functions for managing expression functions (CFG_EFUNC)
4//! in the Senzing configuration JSON.
5
6use crate::error::SzConfigError;
7use crate::helpers::{
8    add_to_config_array, delete_from_config_array, find_in_config_array, get_next_id,
9};
10use serde_json::{Value, json};
11
12// ============================================================================
13// Parameter Structs
14// ============================================================================
15
16/// Parameters for adding an expression function
17#[derive(Debug, Clone, Default)]
18pub struct AddExpressionFunctionParams<'a> {
19    pub connect_str: &'a str,
20    pub description: Option<&'a str>,
21    pub language: Option<&'a str>,
22}
23
24impl<'a> TryFrom<&'a Value> for AddExpressionFunctionParams<'a> {
25    type Error = SzConfigError;
26
27    fn try_from(json: &'a Value) -> Result<Self, SzConfigError> {
28        Ok(Self {
29            connect_str: json
30                .get("connectStr")
31                .and_then(|v| v.as_str())
32                .ok_or_else(|| SzConfigError::MissingField("connectStr".to_string()))?,
33            description: json.get("description").and_then(|v| v.as_str()),
34            language: json.get("language").and_then(|v| v.as_str()),
35        })
36    }
37}
38
39/// Parameters for setting an expression function
40#[derive(Debug, Clone, Default)]
41pub struct SetExpressionFunctionParams<'a> {
42    pub connect_str: Option<&'a str>,
43    pub description: Option<&'a str>,
44    pub language: Option<&'a str>,
45}
46
47/// Add a new expression function
48///
49/// # Arguments
50/// * `config_json` - The configuration JSON string
51/// * `efunc_code` - Function code (will be uppercased)
52/// * `params` - Function parameters (connect_str required, others optional)
53///
54/// # Returns
55/// Result with modified JSON string and the new function record
56///
57/// # Errors
58/// Returns error if function already exists or JSON is invalid
59pub fn add_expression_function(
60    config_json: &str,
61    efunc_code: &str,
62    params: AddExpressionFunctionParams,
63) -> Result<(String, Value), SzConfigError> {
64    let efunc_code = efunc_code.to_uppercase();
65
66    // Check if function already exists
67    if find_in_config_array(config_json, "CFG_EFUNC", "EFUNC_CODE", &efunc_code)?.is_some() {
68        return Err(SzConfigError::validation(format!(
69            "Expression function already exists: {efunc_code}"
70        )));
71    }
72
73    // Get next EFUNC_ID
74    let config_data: Value =
75        serde_json::from_str(config_json).map_err(|e| SzConfigError::json_parse(e.to_string()))?;
76    let efunc_id = get_next_id(&config_data, "G2_CONFIG.CFG_EFUNC", "EFUNC_ID", 1)?;
77
78    // Create new function record
79    let mut new_record = json!({
80        "EFUNC_ID": efunc_id,
81        "EFUNC_CODE": efunc_code,
82        "CONNECT_STR": params.connect_str,
83    });
84
85    // Add optional fields (always present, null when not specified)
86    new_record["EFUNC_DESC"] = match params.description {
87        Some(desc) => json!(desc),
88        None => Value::Null,
89    };
90    new_record["LANGUAGE"] = match params.language {
91        Some(lang) => json!(lang),
92        None => Value::Null,
93    };
94
95    // Add to CFG_EFUNC
96    let modified_json = add_to_config_array(config_json, "CFG_EFUNC", new_record.clone())?;
97
98    Ok((modified_json, new_record))
99}
100
101/// Delete an expression function by code
102///
103/// # Arguments
104/// * `config_json` - The configuration JSON string
105/// * `efunc_code` - Function code to delete
106///
107/// # Returns
108/// Result with modified JSON string and the deleted function record
109///
110/// # Errors
111/// Returns error if function not found or JSON is invalid
112pub fn delete_expression_function(
113    config_json: &str,
114    efunc_code: &str,
115) -> Result<(String, Value), SzConfigError> {
116    let efunc_code = efunc_code.to_uppercase();
117
118    // Find the function
119    let function = find_in_config_array(config_json, "CFG_EFUNC", "EFUNC_CODE", &efunc_code)?
120        .ok_or_else(|| {
121            SzConfigError::not_found(format!("Expression function not found: {efunc_code}"))
122        })?;
123
124    // Delete from CFG_EFUNC
125    let modified_json =
126        delete_from_config_array(config_json, "CFG_EFUNC", "EFUNC_CODE", &efunc_code)?;
127
128    Ok((modified_json, function))
129}
130
131/// Get an expression function by code
132///
133/// # Arguments
134/// * `config_json` - The configuration JSON string
135/// * `efunc_code` - Function code to retrieve
136///
137/// # Returns
138/// Result with the function record
139///
140/// # Errors
141/// Returns error if function not found or JSON is invalid
142pub fn get_expression_function(
143    config_json: &str,
144    efunc_code: &str,
145) -> Result<Value, SzConfigError> {
146    let efunc_code = efunc_code.to_uppercase();
147
148    find_in_config_array(config_json, "CFG_EFUNC", "EFUNC_CODE", &efunc_code)?.ok_or_else(|| {
149        SzConfigError::not_found(format!("Expression function not found: {efunc_code}"))
150    })
151}
152
153/// List all expression functions
154///
155/// # Arguments
156/// * `config_json` - The configuration JSON string
157///
158/// # Returns
159/// Result with vector of function records in display format
160///
161/// # Errors
162/// Returns error if JSON is invalid
163pub fn list_expression_functions(config_json: &str) -> Result<Vec<Value>, SzConfigError> {
164    let config_data: Value =
165        serde_json::from_str(config_json).map_err(|e| SzConfigError::json_parse(e.to_string()))?;
166
167    let items: Vec<Value> = if let Some(g2_config) = config_data.get("G2_CONFIG")
168        && let Some(array) = g2_config.get("CFG_EFUNC")
169        && let Some(items) = array.as_array()
170    {
171        items
172            .iter()
173            .map(|item| {
174                json!({
175                    "id": item.get("EFUNC_ID").and_then(|v| v.as_i64()).unwrap_or(0),
176                    "function": item.get("EFUNC_CODE").and_then(|v| v.as_str()).unwrap_or(""),
177                    "connectStr": item.get("CONNECT_STR").and_then(|v| v.as_str()).unwrap_or(""),
178                    "language": item.get("LANGUAGE").and_then(|v| v.as_str()).unwrap_or("")
179                })
180            })
181            .collect()
182    } else {
183        Vec::new()
184    };
185
186    Ok(items)
187}
188
189/// Set (update) an expression function
190///
191/// # Arguments
192/// * `config_json` - The configuration JSON string
193/// * `efunc_code` - Function code to update
194/// * `params` - Function parameters to update (all optional)
195///
196/// # Returns
197/// Result with modified JSON string and the updated function record
198///
199/// # Errors
200/// Returns error if function not found or JSON is invalid
201pub fn set_expression_function(
202    config_json: &str,
203    efunc_code: &str,
204    params: SetExpressionFunctionParams,
205) -> Result<(String, Value), SzConfigError> {
206    let efunc_code = efunc_code.to_uppercase();
207
208    // Find existing function
209    let mut function = find_in_config_array(config_json, "CFG_EFUNC", "EFUNC_CODE", &efunc_code)?
210        .ok_or_else(|| {
211        SzConfigError::not_found(format!("Expression function not found: {efunc_code}"))
212    })?;
213
214    // Update fields if provided
215    if let Some(obj) = function.as_object_mut() {
216        if let Some(conn) = params.connect_str {
217            obj.insert("CONNECT_STR".to_string(), json!(conn));
218        }
219        if let Some(desc) = params.description {
220            obj.insert("EFUNC_DESC".to_string(), json!(desc));
221        }
222        if let Some(lang) = params.language {
223            obj.insert("LANGUAGE".to_string(), json!(lang));
224        }
225    }
226
227    // Delete old and add updated
228    let temp_json = delete_from_config_array(config_json, "CFG_EFUNC", "EFUNC_CODE", &efunc_code)?;
229    let modified_json = add_to_config_array(&temp_json, "CFG_EFUNC", function.clone())?;
230
231    Ok((modified_json, function))
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237
238    fn get_test_config() -> String {
239        json!({
240            "G2_CONFIG": {
241                "CFG_EFUNC": [
242                    {
243                        "EFUNC_ID": 1,
244                        "EFUNC_CODE": "EXPR_FEAT",
245                        "CONNECT_STR": "g2ExprFeat",
246                        "LANGUAGE": "en"
247                    }
248                ]
249            }
250        })
251        .to_string()
252    }
253
254    #[test]
255    fn test_add_expression_function() {
256        let config = get_test_config();
257        let result = add_expression_function(
258            &config,
259            "custom_expr",
260            AddExpressionFunctionParams {
261                connect_str: "g2CustomExpr",
262                description: Some("Custom expression"),
263                language: Some("en"),
264            },
265        );
266        assert!(result.is_ok());
267        let (modified, record) = result.unwrap();
268        assert!(modified.contains("CUSTOM_EXPR"));
269        assert_eq!(record["EFUNC_CODE"], "CUSTOM_EXPR");
270    }
271
272    #[test]
273    fn test_list_expression_functions() {
274        let config = get_test_config();
275        let result = list_expression_functions(&config);
276        assert!(result.is_ok());
277        let items = result.unwrap();
278        assert_eq!(items.len(), 1);
279        assert_eq!(items[0]["function"], "EXPR_FEAT");
280    }
281}