libsubconverter/utils/
tribool.rs

1//! Tribool utility traits for Option<T>
2//!
3//! This module provides utility traits to extend Option<T> with tribool-like functionality,
4//! similar to the C++ tribool implementation in the original subconverter.
5
6use serde_json::{Map, Value as JsonValue};
7
8/// Trait for types that can be applied to a JSON object conditionally
9pub trait JsonApplicable {
10    /// Apply a value to a JSON object with the specified key
11    /// only if it meets some condition (usually non-empty or defined)
12    ///
13    /// # Arguments
14    ///
15    /// * `json` - The JSON object to modify
16    /// * `key` - The key to set in the JSON object
17    ///
18    /// # Returns
19    ///
20    /// true if the value was applied, false otherwise
21    fn apply_json(&self, json: &mut JsonValue, key: &str) -> bool;
22}
23
24// Implementation for strings - only apply if non-empty
25impl JsonApplicable for String {
26    fn apply_json(&self, json: &mut JsonValue, key: &str) -> bool {
27        if !self.is_empty() {
28            if let JsonValue::Object(map) = json {
29                map.insert(key.to_string(), JsonValue::String(self.clone()));
30                true
31            } else {
32                false
33            }
34        } else {
35            false
36        }
37    }
38}
39
40// Implementation for &str - only apply if non-empty
41impl JsonApplicable for &str {
42    fn apply_json(&self, json: &mut JsonValue, key: &str) -> bool {
43        if !self.is_empty() {
44            if let JsonValue::Object(map) = json {
45                map.insert(key.to_string(), JsonValue::String((*self).to_string()));
46                true
47            } else {
48                false
49            }
50        } else {
51            false
52        }
53    }
54}
55
56// Implementation for Option<String> - only apply if Some and non-empty
57impl JsonApplicable for Option<String> {
58    fn apply_json(&self, json: &mut JsonValue, key: &str) -> bool {
59        if let Some(value) = self {
60            value.apply_json(json, key)
61        } else {
62            false
63        }
64    }
65}
66
67// Implementation for Option<&str> - only apply if Some and non-empty
68impl JsonApplicable for Option<&str> {
69    fn apply_json(&self, json: &mut JsonValue, key: &str) -> bool {
70        if let Some(value) = self {
71            value.apply_json(json, key)
72        } else {
73            false
74        }
75    }
76}
77
78// Implementation for numbers - only apply if non-zero
79impl JsonApplicable for u16 {
80    fn apply_json(&self, json: &mut JsonValue, key: &str) -> bool {
81        if *self > 0 {
82            if let JsonValue::Object(map) = json {
83                map.insert(key.to_string(), JsonValue::Number((*self).into()));
84                true
85            } else {
86                false
87            }
88        } else {
89            false
90        }
91    }
92}
93
94impl JsonApplicable for u32 {
95    fn apply_json(&self, json: &mut JsonValue, key: &str) -> bool {
96        if *self > 0 {
97            if let JsonValue::Object(map) = json {
98                map.insert(key.to_string(), JsonValue::Number((*self).into()));
99                true
100            } else {
101                false
102            }
103        } else {
104            false
105        }
106    }
107}
108
109/// Extension trait for Option<T> that adds tribool-like functionality
110pub trait TriboolExt<T> {
111    /// Define a value if the current value is None (undefined)
112    ///
113    /// Similar to the tribool.define() function in C++, this method will set
114    /// the value to the provided default only if the current value is None.
115    ///
116    /// # Arguments
117    ///
118    /// * `default` - The value to use if self is None
119    ///
120    /// # Returns
121    ///
122    /// The existing value if Some, otherwise the default value
123    fn define(&self, default: Option<T>) -> Option<T>
124    where
125        T: Clone;
126
127    /// Apply this tribool value to a JSON object if it's defined (Some)
128    ///
129    /// # Arguments
130    ///
131    /// * `obj` - The JSON object to modify
132    /// * `key` - The key to set in the JSON object
133    ///
134    /// # Returns
135    ///
136    /// true if the value was applied, false if it was undefined (None)
137    fn apply_to_json(&self, obj: &mut Map<String, JsonValue>, key: &str) -> bool
138    where
139        T: Into<JsonValue> + Clone;
140
141    /// Apply this tribool value to a JSON Value object if it's defined (Some)
142    ///
143    /// # Arguments
144    ///
145    /// * `obj` - The JSON Value object to modify (must be an object type)
146    /// * `key` - The key to set in the JSON object
147    ///
148    /// # Returns
149    ///
150    /// true if the value was applied, false if it was undefined (None) or the target wasn't a JSON object
151    fn apply_to_json_value(&self, obj: &mut JsonValue, key: &str) -> bool
152    where
153        T: Into<JsonValue> + Clone;
154}
155pub trait OptionSetExt<T> {
156    fn set_if_some(&mut self, src: Option<T>);
157}
158
159impl<T> OptionSetExt<T> for Option<T> {
160    fn set_if_some(&mut self, src: Option<T>) {
161        if let Some(value) = src {
162            *self = Some(value);
163        }
164    }
165}
166
167impl<T> TriboolExt<T> for Option<T> {
168    fn define(&self, default: Option<T>) -> Option<T>
169    where
170        T: Clone,
171    {
172        match self {
173            Some(value) => Some(value.clone()),
174            None => default,
175        }
176    }
177
178    fn apply_to_json(&self, obj: &mut Map<String, JsonValue>, key: &str) -> bool
179    where
180        T: Into<JsonValue> + Clone,
181    {
182        match self {
183            Some(value) => {
184                let json_value = value.clone().into();
185                obj.insert(key.to_string(), json_value);
186                true
187            }
188            None => false,
189        }
190    }
191
192    fn apply_to_json_value(&self, obj: &mut JsonValue, key: &str) -> bool
193    where
194        T: Into<JsonValue> + Clone,
195    {
196        if let JsonValue::Object(map) = obj {
197            self.apply_to_json(map, key)
198        } else {
199            false
200        }
201    }
202}
203
204// Implement JsonApplicable for Option<bool> tribools
205impl JsonApplicable for Option<bool> {
206    fn apply_json(&self, json: &mut JsonValue, key: &str) -> bool {
207        if let Some(value) = self {
208            if let JsonValue::Object(map) = json {
209                map.insert(key.to_string(), JsonValue::Bool(*value));
210                true
211            } else {
212                false
213            }
214        } else {
215            false
216        }
217    }
218}
219
220/// Extend Option<bool> with tribool-specific methods
221pub trait BoolTriboolExt: TriboolExt<bool> {
222    /// Get the boolean value with a default if undefined
223    ///
224    /// # Arguments
225    ///
226    /// * `default` - The default value to use if undefined
227    ///
228    /// # Returns
229    ///
230    /// The boolean value or the default
231    fn get_or(&self, default: bool) -> bool;
232
233    /// Check if the tribool is undefined
234    ///
235    /// # Returns
236    ///
237    /// true if the value is None, false otherwise
238    fn is_undef(&self) -> bool;
239
240    /// Get the string representation of the tribool
241    ///
242    /// # Returns
243    ///
244    /// "true" if true, "false" if false, empty string if undefined
245    fn get_str(&self) -> String;
246}
247
248impl BoolTriboolExt for Option<bool> {
249    fn get_or(&self, default: bool) -> bool {
250        self.unwrap_or(default)
251    }
252
253    fn is_undef(&self) -> bool {
254        self.is_none()
255    }
256
257    fn get_str(&self) -> String {
258        match self {
259            Some(true) => "true".to_string(),
260            Some(false) => "false".to_string(),
261            None => String::new(),
262        }
263    }
264}