rusty_rules/
value.rs

1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::collections::BTreeMap;
4use std::hash::Hash;
5use std::net::{AddrParseError, IpAddr};
6use std::result::Result as StdResult;
7use std::str::FromStr;
8
9use serde_json::{Number, Value as JsonValue};
10
11/// Represents possible values returned by fetchers
12#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
13pub enum Value<'a> {
14    #[default]
15    None,
16    String(Cow<'a, str>),
17    Number(Number),
18    Bool(bool),
19    Ip(IpAddr),
20    Array(Vec<Value<'a>>),
21    Map(BTreeMap<String, Value<'a>>),
22}
23
24impl<'a> Value<'a> {
25    pub(crate) fn into_static(self) -> Value<'static> {
26        match self {
27            Value::None => Value::None,
28            Value::String(s) => Value::String(Cow::Owned(s.into_owned())),
29            Value::Number(n) => Value::Number(n),
30            Value::Bool(b) => Value::Bool(b),
31            Value::Ip(ip) => Value::Ip(ip),
32            Value::Array(arr) => Value::Array(arr.into_iter().map(|v| v.into_static()).collect()),
33            Value::Map(map) => {
34                Value::Map(map.into_iter().map(|(k, v)| (k, v.into_static())).collect())
35            }
36        }
37    }
38
39    /// Returns the value as a string if it is a string
40    pub fn as_str(&self) -> Option<&str> {
41        match self {
42            Value::String(s) => Some(s),
43            _ => None,
44        }
45    }
46
47    /// Returns the value as an integer if it is a number
48    pub fn as_i64(&self) -> Option<i64> {
49        match self {
50            Value::Number(n) => n.as_i64(),
51            _ => None,
52        }
53    }
54
55    /// Returns the value as a float if it is a number
56    pub fn as_f64(&self) -> Option<f64> {
57        match self {
58            Value::Number(n) => n.as_f64(),
59            _ => None,
60        }
61    }
62
63    /// Returns the value as a boolean if it is a boolean
64    pub fn as_bool(&self) -> Option<bool> {
65        match self {
66            Value::Bool(b) => Some(*b),
67            _ => None,
68        }
69    }
70
71    /// Returns the value as an IP address if it is an IP address
72    pub fn as_ip(&self) -> Option<IpAddr> {
73        match self {
74            Value::Ip(ip) => Some(*ip),
75            _ => None,
76        }
77    }
78
79    /// Converts the value to an IP address, returning an error if it cannot be converted
80    pub fn to_ip(&self) -> Result<IpAddr, AddrParseError> {
81        match self {
82            Value::String(s) => IpAddr::from_str(s),
83            Value::Ip(ip) => Ok(*ip),
84            _ => IpAddr::from_str(""), // Return an error for non-string or non-IP values
85        }
86    }
87
88    /// Returns the value as an array if it is an array
89    pub fn as_array(&self) -> Option<&Vec<Value<'a>>> {
90        match self {
91            Value::Array(arr) => Some(arr),
92            _ => None,
93        }
94    }
95
96    /// Returns the value as a mutable array if it is an array
97    pub fn as_array_mut(&mut self) -> Option<&mut Vec<Value<'a>>> {
98        match self {
99            Value::Array(arr) => Some(arr),
100            _ => None,
101        }
102    }
103
104    /// Returns the value as a map if it is a map
105    pub fn as_map(&self) -> Option<&BTreeMap<String, Value<'a>>> {
106        match self {
107            Value::Map(map) => Some(map),
108            _ => None,
109        }
110    }
111
112    /// Returns the value as a mutable map if it is a map
113    pub fn as_map_mut(&mut self) -> Option<&mut BTreeMap<String, Value<'a>>> {
114        match self {
115            Value::Map(map) => Some(map),
116            _ => None,
117        }
118    }
119}
120
121impl PartialOrd for Value<'_> {
122    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
123        match (self, other) {
124            (Value::None, Value::None) => Some(Ordering::Equal),
125            (Value::String(s), Value::String(t)) => s.partial_cmp(t),
126            (Value::Number(i), Value::Number(j)) => {
127                if let (Some(i), Some(j)) = (i.as_i64(), j.as_i64()) {
128                    i.partial_cmp(&j)
129                } else if let (Some(i), Some(j)) = (i.as_f64(), j.as_f64()) {
130                    i.partial_cmp(&j)
131                } else {
132                    None
133                }
134            }
135            (Value::Bool(i), Value::Bool(j)) => i.partial_cmp(j),
136            (Value::Ip(i), Value::Ip(j)) => i.partial_cmp(j),
137            (Value::Array(i), Value::Array(j)) => i.partial_cmp(j),
138            (Value::Map(i), Value::Map(j)) => i.partial_cmp(j),
139            _ => None,
140        }
141    }
142}
143
144impl From<JsonValue> for Value<'_> {
145    fn from(value: JsonValue) -> Self {
146        match value {
147            JsonValue::Null => Value::None,
148            JsonValue::String(s) => Value::String(Cow::Owned(s)),
149            JsonValue::Number(n) => Value::Number(n),
150            JsonValue::Bool(b) => Value::Bool(b),
151            JsonValue::Array(arr) => {
152                let arr = arr.into_iter().map(|v| v.into()).collect();
153                Value::Array(arr)
154            }
155            JsonValue::Object(obj) => {
156                let map = obj.into_iter().map(|(k, v)| (k, v.into())).collect();
157                Value::Map(map)
158            }
159        }
160    }
161}
162
163impl<'a> From<&'a JsonValue> for Value<'a> {
164    fn from(value: &'a JsonValue) -> Self {
165        match value {
166            JsonValue::Null => Value::None,
167            JsonValue::String(s) => Value::String(Cow::Borrowed(s)),
168            JsonValue::Number(n) => Value::Number(n.clone()),
169            JsonValue::Bool(b) => Value::Bool(*b),
170            JsonValue::Array(arr) => Value::Array(arr.iter().map(|v| v.into()).collect()),
171            JsonValue::Object(obj) => {
172                let map = obj.iter().map(|(k, v)| (k.clone(), v.into())).collect();
173                Value::Map(map)
174            }
175        }
176    }
177}
178
179impl From<Value<'_>> for JsonValue {
180    fn from(value: Value<'_>) -> Self {
181        match value {
182            Value::None => JsonValue::Null,
183            Value::String(s) => JsonValue::String(s.into_owned()),
184            Value::Number(n) => JsonValue::Number(n),
185            Value::Bool(b) => JsonValue::Bool(b),
186            Value::Ip(ip) => JsonValue::String(ip.to_string()),
187            Value::Array(arr) => JsonValue::Array(arr.into_iter().map(|v| v.into()).collect()),
188            Value::Map(map) => {
189                JsonValue::Object(map.into_iter().map(|(k, v)| (k, v.into())).collect())
190            }
191        }
192    }
193}
194
195impl From<String> for Value<'_> {
196    #[inline(always)]
197    fn from(s: String) -> Self {
198        Value::String(Cow::Owned(s))
199    }
200}
201
202impl<'a> From<&'a String> for Value<'a> {
203    #[inline(always)]
204    fn from(s: &'a String) -> Self {
205        Value::String(Cow::Borrowed(s))
206    }
207}
208
209impl<'a> From<&'a str> for Value<'a> {
210    #[inline(always)]
211    fn from(s: &'a str) -> Self {
212        Value::String(Cow::Borrowed(s))
213    }
214}
215
216impl<'a> From<Cow<'a, str>> for Value<'a> {
217    #[inline(always)]
218    fn from(s: Cow<'a, str>) -> Self {
219        Value::String(s)
220    }
221}
222
223macro_rules! impl_from_int {
224    ($($ty:ty),*) => {
225        $(
226            impl From<$ty> for Value<'_> {
227                #[inline(always)]
228                fn from(i: $ty) -> Self {
229                    Value::Number(Number::from(i))
230                }
231            }
232        )*
233    };
234}
235
236impl_from_int!(i8, u8, i16, u16, i32, u32, i64, u64, isize, usize);
237
238impl TryFrom<f64> for Value<'_> {
239    type Error = ();
240
241    #[inline(always)]
242    fn try_from(f: f64) -> StdResult<Self, Self::Error> {
243        Ok(Value::Number(Number::from_f64(f).ok_or(())?))
244    }
245}
246
247impl From<bool> for Value<'_> {
248    #[inline(always)]
249    fn from(b: bool) -> Self {
250        Value::Bool(b)
251    }
252}
253
254impl From<IpAddr> for Value<'_> {
255    #[inline(always)]
256    fn from(ip: IpAddr) -> Self {
257        Value::Ip(ip)
258    }
259}
260
261impl<'a, T> From<Option<T>> for Value<'a>
262where
263    T: Into<Value<'a>>,
264{
265    #[inline(always)]
266    fn from(opt: Option<T>) -> Self {
267        match opt {
268            Some(v) => v.into(),
269            None => Value::None,
270        }
271    }
272}
273
274impl<'a, T: Into<Value<'a>>> From<Vec<T>> for Value<'a> {
275    #[inline(always)]
276    fn from(arr: Vec<T>) -> Self {
277        Value::Array(arr.into_iter().map(|v| v.into()).collect())
278    }
279}
280
281impl<'a, T: Into<Value<'a>>> From<BTreeMap<String, T>> for Value<'a> {
282    #[inline(always)]
283    fn from(map: BTreeMap<String, T>) -> Self {
284        Value::Map(map.into_iter().map(|(k, v)| (k, v.into())).collect())
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291
292    #[test]
293    fn test_value() {
294        // None value
295        assert_eq!(Value::None, Value::None);
296
297        // String value
298        let val = Value::from("true").into_static();
299        assert_eq!(val.as_str(), Some("true"));
300        assert_eq!(val.as_i64(), None);
301        assert_eq!(val.as_f64(), None);
302        assert_eq!(val.as_bool(), None);
303        assert!(val.to_ip().is_err());
304        assert!(Value::from("127.0.0.1").to_ip().is_ok());
305        assert!(Value::from("0") < Value::from("1"));
306
307        // Number value
308        let val = Value::from(42).into_static();
309        assert_eq!(val.as_str(), None);
310        assert_eq!(val.as_i64(), Some(42));
311        assert_eq!(val.as_f64(), Some(42.0));
312        assert_eq!(val.as_bool(), None);
313        assert!(val.to_ip().is_err());
314        assert!(Value::from(42) < Value::from(43));
315        assert!(Value::try_from(0.001).unwrap() > Value::from(0));
316
317        // Bool value
318        let val = Value::from(true).into_static();
319        assert_eq!(val.as_str(), None);
320        assert_eq!(val.as_i64(), None);
321        assert_eq!(val.as_f64(), None);
322        assert_eq!(val.as_bool(), Some(true));
323        assert!(val.to_ip().is_err());
324        assert!(Value::from(false) < Value::from(true));
325
326        // IP value
327        let val = Value::from(IpAddr::from_str("127.0.0.1").unwrap()).into_static();
328        assert_eq!(val.as_str(), None);
329        assert_eq!(val.as_i64(), None);
330        assert_eq!(val.as_f64(), None);
331        assert_eq!(val.as_bool(), None);
332        assert_eq!(val.to_ip().unwrap(), "127.0.0.1".parse::<IpAddr>().unwrap());
333        assert!(
334            Value::from("127.0.0.1".parse::<IpAddr>().unwrap())
335                < Value::from("127.0.0.2".parse::<IpAddr>().unwrap())
336        );
337
338        // Array value
339        let mut val = Value::from(vec!["a", "b", "c"]).into_static();
340        assert_eq!(val.as_array().unwrap().len(), 3);
341        assert_eq!(val.as_map(), None);
342        if let Some(arr) = val.as_array_mut() {
343            arr.push(Value::from("d"));
344        }
345        assert_eq!(val.as_array().unwrap().len(), 4);
346        assert!(Value::from(vec![1, 2, 3]) < Value::from(vec![1, 2, 4]));
347
348        // Map value
349        let mut val = Value::from(BTreeMap::from_iter(vec![
350            ("key1".to_string(), Value::from("value1")),
351            ("key2".to_string(), Value::from(42)),
352        ]))
353        .into_static();
354        assert_eq!(val.as_map().unwrap().len(), 2);
355        assert_eq!(val.as_array(), None);
356        if let Some(m) = val.as_map_mut() {
357            m.insert("key3".to_string(), Value::from(true));
358        }
359        assert_eq!(val.as_map().unwrap().len(), 3);
360        assert!(
361            Value::from(BTreeMap::from_iter(vec![("a".to_string(), Value::from(1))]))
362                < Value::from(BTreeMap::from_iter(vec![("a".to_string(), Value::from(2))]))
363        );
364
365        // From/To serde_json::Value
366        let json_val: JsonValue = serde_json::json!({
367            "string": "value",
368            "number": 42,
369            "bool": true,
370            "array": [1, 2, 3],
371            "map": { "key": "value" },
372            "null": null
373        });
374        assert_eq!(Value::from(&json_val), Value::from(json_val.clone()));
375        let back_to_json: JsonValue = Value::from(json_val.clone()).into();
376        assert_eq!(back_to_json, json_val);
377    }
378}