Skip to main content

sz_configtool_lib/
behavior_overrides.rs

1//! Behavior override operations (CFG_FBOVR)
2//!
3//! Functions for managing feature behavior overrides based on usage types.
4//! Overrides allow different behavior codes for features depending on context
5//! (e.g., BUSINESS vs MOBILE usage).
6
7use crate::error::{Result, SzConfigError};
8use crate::helpers;
9use serde_json::{Value, json};
10
11// ============================================================================
12// Parameter Structs
13// ============================================================================
14
15/// Parameters for adding a behavior override
16#[derive(Debug, Clone)]
17pub struct AddBehaviorOverrideParams<'a> {
18    pub feature_code: &'a str,
19    pub usage_type: &'a str,
20    pub behavior: &'a str,
21}
22
23impl<'a> AddBehaviorOverrideParams<'a> {
24    pub fn new(feature_code: &'a str, usage_type: &'a str, behavior: &'a str) -> Self {
25        Self {
26            feature_code,
27            usage_type,
28            behavior,
29        }
30    }
31}
32
33/// Add a behavior override for a feature based on usage type
34///
35/// # Arguments
36/// * `config_json` - Configuration JSON string
37/// * `params` - Override parameters (feature_code, usage_type, behavior)
38///
39/// # Returns
40/// Modified configuration JSON string
41///
42/// # Errors
43/// - `NotFound` if feature doesn't exist
44/// - `AlreadyExists` if override already exists for this feature+usage combination
45/// - `InvalidInput` if behavior code is invalid
46///
47/// # Example
48/// ```no_run
49/// use sz_configtool_lib::behavior_overrides::{add_behavior_override, AddBehaviorOverrideParams};
50/// let config = r#"{"G2_CONFIG":{"CFG_FTYPE":[...], "CFG_FBOVR":[]}}"#;
51/// let updated = add_behavior_override(
52///     &config,
53///     AddBehaviorOverrideParams::new("PLACEKEY", "BUSINESS", "F1E")
54/// )?;
55/// # Ok::<(), sz_configtool_lib::error::SzConfigError>(())
56/// ```
57pub fn add_behavior_override(
58    config_json: &str,
59    params: AddBehaviorOverrideParams,
60) -> Result<String> {
61    let config: Value =
62        serde_json::from_str(config_json).map_err(|e| SzConfigError::JsonParse(e.to_string()))?;
63
64    // Lookup FTYPE_ID from feature code
65    let ftype_id = helpers::lookup_feature_id(config_json, params.feature_code)?;
66
67    // Parse behavior code into frequency, exclusivity, stability
68    let (frequency, exclusivity, stability) = parse_behavior_code(params.behavior)?;
69
70    let utype_upper = params.usage_type.to_uppercase();
71
72    // Check for existing override for this feature+usage combination
73    let fbovr_array = config
74        .get("G2_CONFIG")
75        .and_then(|g| g.get("CFG_FBOVR"))
76        .and_then(|v| v.as_array())
77        .ok_or_else(|| SzConfigError::MissingSection("CFG_FBOVR".to_string()))?;
78
79    if fbovr_array.iter().any(|item| {
80        item["FTYPE_ID"].as_i64() == Some(ftype_id)
81            && item["UTYPE_CODE"].as_str() == Some(&utype_upper)
82    }) {
83        return Err(SzConfigError::AlreadyExists(format!(
84            "Behavior override already exists for feature {} with usage type {}",
85            params.feature_code, utype_upper
86        )));
87    }
88
89    // Create override record
90    let override_record = json!({
91        "FTYPE_ID": ftype_id,
92        "UTYPE_CODE": utype_upper,
93        "FTYPE_FREQ": frequency,
94        "FTYPE_EXCL": exclusivity,
95        "FTYPE_STAB": stability
96    });
97
98    // Add to CFG_FBOVR
99    helpers::add_to_config_array(config_json, "CFG_FBOVR", override_record)
100}
101
102/// Delete a behavior override for a feature and usage type
103///
104/// # Arguments
105/// * `config_json` - Configuration JSON string
106/// * `feature_code` - Feature code
107/// * `usage_type` - Usage type code
108///
109/// # Returns
110/// Modified configuration JSON string
111///
112/// # Errors
113/// - `NotFound` if feature or override doesn't exist
114pub fn delete_behavior_override(
115    config_json: &str,
116    feature_code: &str,
117    usage_type: &str,
118) -> Result<String> {
119    let mut config: Value =
120        serde_json::from_str(config_json).map_err(|e| SzConfigError::JsonParse(e.to_string()))?;
121
122    // Lookup FTYPE_ID
123    let ftype_id = helpers::lookup_feature_id(config_json, feature_code)?;
124    let utype_upper = usage_type.to_uppercase();
125
126    let fbovr_array = config
127        .get_mut("G2_CONFIG")
128        .and_then(|g| g.get_mut("CFG_FBOVR"))
129        .and_then(|v| v.as_array_mut())
130        .ok_or_else(|| SzConfigError::MissingSection("CFG_FBOVR".to_string()))?;
131
132    let original_len = fbovr_array.len();
133    fbovr_array.retain(|item| {
134        !(item["FTYPE_ID"].as_i64() == Some(ftype_id)
135            && item["UTYPE_CODE"].as_str() == Some(&utype_upper))
136    });
137
138    if fbovr_array.len() == original_len {
139        return Err(SzConfigError::NotFound(format!(
140            "Behavior override not found for feature {feature_code} with usage type {utype_upper}"
141        )));
142    }
143
144    serde_json::to_string(&config).map_err(|e| SzConfigError::JsonParse(e.to_string()))
145}
146
147/// Get a specific behavior override
148///
149/// # Arguments
150/// * `config_json` - Configuration JSON string
151/// * `feature_code` - Feature code
152/// * `usage_type` - Usage type code
153///
154/// # Returns
155/// JSON Value representing the behavior override
156pub fn get_behavior_override(
157    config_json: &str,
158    feature_code: &str,
159    usage_type: &str,
160) -> Result<Value> {
161    let config: Value =
162        serde_json::from_str(config_json).map_err(|e| SzConfigError::JsonParse(e.to_string()))?;
163
164    let ftype_id = helpers::lookup_feature_id(config_json, feature_code)?;
165    let utype_upper = usage_type.to_uppercase();
166
167    config
168        .get("G2_CONFIG")
169        .and_then(|g| g.get("CFG_FBOVR"))
170        .and_then(|v| v.as_array())
171        .ok_or_else(|| SzConfigError::MissingSection("CFG_FBOVR".to_string()))?
172        .iter()
173        .find(|item| {
174            item["FTYPE_ID"].as_i64() == Some(ftype_id)
175                && item["UTYPE_CODE"].as_str() == Some(&utype_upper)
176        })
177        .cloned()
178        .ok_or_else(|| {
179            SzConfigError::NotFound(format!(
180                "Behavior override not found for feature {feature_code} with usage type {utype_upper}"
181            ))
182        })
183}
184
185/// List all behavior overrides
186///
187/// # Arguments
188/// * `config_json` - Configuration JSON string
189///
190/// # Returns
191/// Vector of JSON Values representing behavior overrides, sorted by FTYPE_ID
192pub fn list_behavior_overrides(config_json: &str) -> Result<Vec<Value>> {
193    let config: Value =
194        serde_json::from_str(config_json).map_err(|e| SzConfigError::JsonParse(e.to_string()))?;
195
196    let fbovr_array = config
197        .get("G2_CONFIG")
198        .and_then(|g| g.get("CFG_FBOVR"))
199        .and_then(|v| v.as_array())
200        .ok_or_else(|| SzConfigError::MissingSection("CFG_FBOVR".to_string()))?;
201
202    let mut result: Vec<Value> = fbovr_array.to_vec();
203
204    // Sort by FTYPE_ID
205    result.sort_by_key(|item| item["FTYPE_ID"].as_i64().unwrap_or(0));
206
207    Ok(result)
208}
209
210/// Parse a behavior code string into (frequency, exclusivity, stability)
211///
212/// Valid frequency codes: A1, F1, FF, FM, FVM, NONE, NAME
213/// E suffix means EXCLUSIVITY = "Yes"
214/// S suffix means STABILITY = "Yes"
215///
216/// # Arguments
217/// * `behavior` - Behavior code (e.g., "FM", "F1E", "F1ES", "NAME")
218///
219/// # Returns
220/// Tuple of (frequency, exclusivity, stability)
221///
222/// # Errors
223/// - `InvalidInput` if behavior code is invalid
224fn parse_behavior_code(behavior: &str) -> Result<(&'static str, &'static str, &'static str)> {
225    let mut code = behavior.to_uppercase();
226    let mut exclusivity = "No";
227    let mut stability = "No";
228
229    // Special cases that don't get E/S parsing
230    if code != "NAME" && code != "NONE" {
231        if code.contains('E') {
232            exclusivity = "Yes";
233            code = code.replace('E', "");
234        }
235        if code.contains('S') {
236            stability = "Yes";
237            code = code.replace('S', "");
238        }
239    }
240
241    // Validate frequency code
242    let frequency: &'static str = match code.as_str() {
243        "A1" => "A1",
244        "F1" => "F1",
245        "FF" => "FF",
246        "FM" => "FM",
247        "FVM" => "FVM",
248        "NONE" => "NONE",
249        "NAME" => "NAME",
250        _ => {
251            return Err(SzConfigError::InvalidInput(format!(
252                "Invalid behavior code '{behavior}'. Valid codes: A1, F1, FF, FM, FVM, NONE, NAME (with optional E/S suffixes)"
253            )));
254        }
255    };
256
257    Ok((frequency, exclusivity, stability))
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    const TEST_CONFIG: &str = r#"{
265  "G2_CONFIG": {
266    "CFG_FTYPE": [
267      {
268        "FTYPE_ID": 1,
269        "FTYPE_CODE": "TEST_FEATURE",
270        "FTYPE_DESC": "Test Feature"
271      }
272    ],
273    "CFG_FBOVR": []
274  }
275}"#;
276
277    #[test]
278    fn test_add_behavior_override() {
279        let config = add_behavior_override(
280            TEST_CONFIG,
281            AddBehaviorOverrideParams::new("TEST_FEATURE", "BUSINESS", "F1E"),
282        )
283        .expect("Failed to add behavior override");
284
285        let config_val: Value = serde_json::from_str(&config).unwrap();
286        let overrides = &config_val["G2_CONFIG"]["CFG_FBOVR"];
287
288        assert_eq!(overrides.as_array().unwrap().len(), 1);
289
290        let override_rec = &overrides[0];
291        assert_eq!(override_rec["FTYPE_ID"], 1);
292        assert_eq!(override_rec["UTYPE_CODE"], "BUSINESS");
293        assert_eq!(override_rec["FTYPE_FREQ"], "F1");
294        assert_eq!(override_rec["FTYPE_EXCL"], "Yes");
295        assert_eq!(override_rec["FTYPE_STAB"], "No");
296    }
297
298    #[test]
299    fn test_delete_behavior_override() {
300        let config = add_behavior_override(
301            TEST_CONFIG,
302            AddBehaviorOverrideParams::new("TEST_FEATURE", "BUSINESS", "F1E"),
303        )
304        .expect("Failed to add");
305
306        let config = delete_behavior_override(&config, "TEST_FEATURE", "BUSINESS")
307            .expect("Failed to delete");
308
309        let config_val: Value = serde_json::from_str(&config).unwrap();
310        let overrides = &config_val["G2_CONFIG"]["CFG_FBOVR"];
311        assert_eq!(overrides.as_array().unwrap().len(), 0);
312    }
313
314    #[test]
315    fn test_list_behavior_overrides() {
316        let config = add_behavior_override(
317            TEST_CONFIG,
318            AddBehaviorOverrideParams::new("TEST_FEATURE", "BUSINESS", "F1E"),
319        )
320        .expect("Failed to add first");
321        let config = add_behavior_override(
322            &config,
323            AddBehaviorOverrideParams::new("TEST_FEATURE", "MOBILE", "FM"),
324        )
325        .expect("Failed to add second");
326
327        let overrides = list_behavior_overrides(&config).expect("Failed to list");
328        assert_eq!(overrides.len(), 2);
329        assert_eq!(overrides[0]["UTYPE_CODE"], "BUSINESS");
330        assert_eq!(overrides[1]["UTYPE_CODE"], "MOBILE");
331    }
332
333    #[test]
334    fn test_parse_behavior_code_simple() {
335        let (freq, excl, stab) = parse_behavior_code("FM").unwrap();
336        assert_eq!(freq, "FM");
337        assert_eq!(excl, "No");
338        assert_eq!(stab, "No");
339    }
340
341    #[test]
342    fn test_parse_behavior_code_with_modifiers() {
343        let (freq, excl, stab) = parse_behavior_code("F1ES").unwrap();
344        assert_eq!(freq, "F1");
345        assert_eq!(excl, "Yes");
346        assert_eq!(stab, "Yes");
347    }
348
349    #[test]
350    fn test_parse_behavior_code_name() {
351        let (freq, excl, stab) = parse_behavior_code("NAME").unwrap();
352        assert_eq!(freq, "NAME");
353        assert_eq!(excl, "No");
354        assert_eq!(stab, "No");
355    }
356
357    #[test]
358    fn test_behavior_override_duplicate() {
359        let config = add_behavior_override(
360            TEST_CONFIG,
361            AddBehaviorOverrideParams::new("TEST_FEATURE", "BUSINESS", "F1E"),
362        )
363        .expect("Failed to add first");
364
365        let result = add_behavior_override(
366            &config,
367            AddBehaviorOverrideParams::new("TEST_FEATURE", "BUSINESS", "FM"),
368        );
369        assert!(result.is_err());
370        assert!(result.unwrap_err().to_string().contains("already exists"));
371    }
372}