ctoolbox/formats/eite/
kv.rs

1// =====================
2// KV ARRAY / KV STRING
3// =====================
4// Original JS KV array format: [ key, value, key, value, ... ]
5// KV string format: "key:value,key2:value2," with (escaped) separators.
6// Full escaping rules depend on strSplitEscaped / strJoinEsc / strJoinEscNoTrailing,
7// which are not yet provided in the snippet. We therefore implement the
8// array-level logic now and leave the string-level split/join until the
9// escape utilities arrive.
10
11use anyhow::{Result, anyhow};
12
13/// Check whether a slice of strings has even length.
14fn is_kv_array(data: &[String]) -> bool {
15    data.len().is_multiple_of(2)
16}
17
18pub fn kv_has_value(data: &[String], key: &str) -> bool {
19    // Mimic original assertKvArray (would throw). We choose panic for now;
20    // can be converted to Result once global error model decided.
21    assert!(is_kv_array(data), "kv_has_value: invalid kv array length");
22    // Walk even indices only
23    let mut i = 0;
24    while i < data.len() {
25        if data.get(i).is_some_and(|k| k == key) {
26            return true;
27        }
28        i += 2;
29    }
30    false
31}
32
33/// Returns value or empty string if not found (matching original forgiving behavior).
34pub fn kv_get_value(data: &[String], key: &str) -> String {
35    assert!(is_kv_array(data), "kv_get_value: invalid kv array length");
36    let mut i = 0;
37    while i + 1 < data.len() {
38        if data[i] == key {
39            return data[i + 1].clone();
40        }
41        i += 2;
42    }
43    String::new()
44}
45
46/// Like `kv_get_value` but asserts key exists (mirrors assertKvHasValue + retrieval).
47pub fn kv_get_defined_value(data: &[String], key: &str) -> Result<String> {
48    if kv_has_value(data, key) {
49        Ok(kv_get_value(data, key))
50    } else {
51        Err(anyhow!("Key '{key}' not found"))
52    }
53}
54
55/// Set (insert or replace) a key/value pair, returning a new `Vec<String>`.
56pub fn kv_set_value(
57    mut data: Vec<String>,
58    key: &str,
59    val: &str,
60) -> Vec<String> {
61    assert!(is_kv_array(&data), "kv_set_value: invalid kv array length");
62    let mut i = 0;
63    while i + 1 < data.len() {
64        if data[i] == key {
65            data[i + 1] = val.to_string();
66            return data;
67        }
68        i += 2;
69    }
70    data.push(key.to_string());
71    data.push(val.to_string());
72    data
73}
74
75#[cfg(test)]
76pub mod tests {
77    use super::*;
78
79    #[crate::ctb_test]
80    fn test_kv_basic() {
81        let data: Vec<String> = vec![];
82        let data = kv_set_value(data, "a", "1");
83        let data = kv_set_value(data, "b", "2");
84        assert!(kv_has_value(&data, "a"));
85        assert_eq!(kv_get_value(&data, "a"), "1");
86        assert_eq!(kv_get_value(&data, "c"), "");
87        let data = kv_set_value(data, "a", "3");
88        assert_eq!(kv_get_value(&data, "a"), "3");
89    }
90
91    #[crate::ctb_test]
92    fn test_kv_has_value_empty_and_odd_length() {
93        let data: Vec<String> = vec![];
94        assert!(!kv_has_value(&data, "x"));
95        let data = vec!["a".to_string()];
96        let result = std::panic::catch_unwind(|| kv_has_value(&data, "a"));
97        assert!(result.is_err());
98    }
99
100    #[crate::ctb_test]
101    fn test_kv_get_value_empty_and_odd_length() {
102        let data: Vec<String> = vec![];
103        assert_eq!(kv_get_value(&data, "x"), "");
104        let data = vec!["a".to_string()];
105        let result = std::panic::catch_unwind(|| kv_get_value(&data, "a"));
106        assert!(result.is_err());
107    }
108
109    #[crate::ctb_test]
110    fn test_kv_get_defined_value_found_and_not_found() {
111        let mut data: Vec<String> = vec![];
112        data = kv_set_value(data, "foo", "bar");
113        assert_eq!(kv_get_defined_value(&data, "foo").unwrap(), "bar");
114        let err = kv_get_defined_value(&data, "baz");
115        assert!(err.is_err());
116    }
117
118    #[crate::ctb_test]
119    fn test_kv_set_value_insert_and_replace() {
120        let mut data: Vec<String> = vec![];
121        data = kv_set_value(data, "x", "1");
122        assert_eq!(data, vec!["x", "1"]);
123        assert_eq!(kv_get_value(&data, "x"), "1");
124        data = kv_set_value(data, "x", "2");
125        assert_eq!(data, vec!["x", "2"]);
126        assert_eq!(kv_get_value(&data, "x"), "2");
127    }
128
129    #[crate::ctb_test]
130    fn test_kv_set_value_odd_length_panics() {
131        let data = vec!["a".to_string()];
132        let result = std::panic::catch_unwind(|| kv_set_value(data, "b", "2"));
133        assert!(result.is_err());
134    }
135
136    #[crate::ctb_test]
137    fn test_kv_array_get_set_mid_elements() {
138        let mut data = vec![
139            "k1".to_string(),
140            "v1".to_string(),
141            "k2".to_string(),
142            "v2".to_string(),
143            "k3".to_string(),
144            "v3".to_string(),
145            "k4".to_string(),
146            "v4".to_string(),
147        ];
148        // Get value for k2 and k3
149        assert_eq!(kv_get_value(&data, "k2"), "v2");
150        assert_eq!(kv_get_value(&data, "k3"), "v3");
151        // Replace value for k2
152        data = kv_set_value(data, "k2", "v2x");
153        assert_eq!(kv_get_value(&data, "k2"), "v2x");
154        // Insert new key-value pair
155        data = kv_set_value(data, "k5", "v5");
156        assert_eq!(kv_get_value(&data, "k5"), "v5");
157        // Check full array
158        assert_eq!(
159            data,
160            vec!["k1", "v1", "k2", "v2x", "k3", "v3", "k4", "v4", "k5", "v5"]
161        );
162    }
163
164    #[crate::ctb_test]
165    fn test_kv_array_get_set_first_and_last() {
166        let mut data = vec![
167            "a".to_string(),
168            "1".to_string(),
169            "b".to_string(),
170            "2".to_string(),
171            "c".to_string(),
172            "3".to_string(),
173            "d".to_string(),
174            "4".to_string(),
175        ];
176        // Get first and last
177        assert_eq!(kv_get_value(&data, "a"), "1");
178        assert_eq!(kv_get_value(&data, "d"), "4");
179        // Replace last
180        data = kv_set_value(data, "d", "44");
181        assert_eq!(kv_get_value(&data, "d"), "44");
182        // Replace first
183        data = kv_set_value(data, "a", "11");
184        assert_eq!(kv_get_value(&data, "a"), "11");
185        // Check full array
186        assert_eq!(data, vec!["a", "11", "b", "2", "c", "3", "d", "44"]);
187    }
188}