solverforge_core/
value.rs

1//! Core value types for SolverForge
2//!
3//! This module defines the language-agnostic [`Value`] enum that represents
4//! all possible values that can be passed between the core library and
5//! language bindings.
6
7use crate::handles::ObjectHandle;
8use rust_decimal::Decimal;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12/// Language-agnostic value representation
13///
14/// This enum can represent any value that needs to be passed between
15/// the core library and language bindings. It maps to JSON types for
16/// easy serialization when communicating with the solver service.
17///
18/// # Example
19///
20/// ```
21/// use solverforge_core::Value;
22///
23/// let int_val = Value::from(42i64);
24/// assert_eq!(int_val.as_int(), Some(42));
25///
26/// let str_val = Value::from("hello");
27/// assert_eq!(str_val.as_str(), Some("hello"));
28/// ```
29#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
30#[serde(untagged)]
31pub enum Value {
32    /// Null/None value
33    #[default]
34    Null,
35    /// Boolean value
36    Bool(bool),
37    /// Integer value (64-bit)
38    Int(i64),
39    /// Floating point value
40    Float(f64),
41    /// Decimal value for precise arithmetic (scores, etc.)
42    #[serde(with = "decimal_serde")]
43    Decimal(Decimal),
44    /// String value
45    String(String),
46    /// Array/List of values
47    Array(Vec<Value>),
48    /// Object/Map of string keys to values
49    Object(HashMap<String, Value>),
50    /// Reference to a host language object
51    #[serde(skip)]
52    ObjectRef(ObjectHandle),
53}
54
55impl Value {
56    /// Check if this value is null
57    pub fn is_null(&self) -> bool {
58        matches!(self, Value::Null)
59    }
60
61    /// Try to get this value as a boolean
62    pub fn as_bool(&self) -> Option<bool> {
63        match self {
64            Value::Bool(b) => Some(*b),
65            _ => None,
66        }
67    }
68
69    /// Try to get this value as an integer
70    pub fn as_int(&self) -> Option<i64> {
71        match self {
72            Value::Int(i) => Some(*i),
73            _ => None,
74        }
75    }
76
77    /// Try to get this value as a float
78    ///
79    /// This will also convert integers to floats.
80    pub fn as_float(&self) -> Option<f64> {
81        match self {
82            Value::Float(f) => Some(*f),
83            Value::Int(i) => Some(*i as f64),
84            _ => None,
85        }
86    }
87
88    /// Try to get this value as a decimal
89    pub fn as_decimal(&self) -> Option<Decimal> {
90        match self {
91            Value::Decimal(d) => Some(*d),
92            _ => None,
93        }
94    }
95
96    /// Try to get this value as a string
97    pub fn as_str(&self) -> Option<&str> {
98        match self {
99            Value::String(s) => Some(s),
100            _ => None,
101        }
102    }
103
104    /// Try to get this value as an array
105    pub fn as_array(&self) -> Option<&Vec<Value>> {
106        match self {
107            Value::Array(arr) => Some(arr),
108            _ => None,
109        }
110    }
111
112    /// Try to get this value as a mutable array
113    pub fn as_array_mut(&mut self) -> Option<&mut Vec<Value>> {
114        match self {
115            Value::Array(arr) => Some(arr),
116            _ => None,
117        }
118    }
119
120    /// Try to get this value as an object/map
121    pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
122        match self {
123            Value::Object(obj) => Some(obj),
124            _ => None,
125        }
126    }
127
128    /// Try to get this value as a mutable object/map
129    pub fn as_object_mut(&mut self) -> Option<&mut HashMap<String, Value>> {
130        match self {
131            Value::Object(obj) => Some(obj),
132            _ => None,
133        }
134    }
135
136    /// Try to get this value as an object handle
137    pub fn as_object_ref(&self) -> Option<ObjectHandle> {
138        match self {
139            Value::ObjectRef(handle) => Some(*handle),
140            _ => None,
141        }
142    }
143}
144
145impl From<bool> for Value {
146    fn from(b: bool) -> Self {
147        Value::Bool(b)
148    }
149}
150
151impl From<i64> for Value {
152    fn from(i: i64) -> Self {
153        Value::Int(i)
154    }
155}
156
157impl From<i32> for Value {
158    fn from(i: i32) -> Self {
159        Value::Int(i as i64)
160    }
161}
162
163impl From<f64> for Value {
164    fn from(f: f64) -> Self {
165        Value::Float(f)
166    }
167}
168
169impl From<f32> for Value {
170    fn from(f: f32) -> Self {
171        Value::Float(f as f64)
172    }
173}
174
175impl From<Decimal> for Value {
176    fn from(d: Decimal) -> Self {
177        Value::Decimal(d)
178    }
179}
180
181impl From<String> for Value {
182    fn from(s: String) -> Self {
183        Value::String(s)
184    }
185}
186
187impl From<&str> for Value {
188    fn from(s: &str) -> Self {
189        Value::String(s.to_string())
190    }
191}
192
193impl<T: Into<Value>> From<Vec<T>> for Value {
194    fn from(v: Vec<T>) -> Self {
195        Value::Array(v.into_iter().map(Into::into).collect())
196    }
197}
198
199impl From<ObjectHandle> for Value {
200    fn from(handle: ObjectHandle) -> Self {
201        Value::ObjectRef(handle)
202    }
203}
204
205impl<K: Into<String>, V: Into<Value>> FromIterator<(K, V)> for Value {
206    fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
207        Value::Object(
208            iter.into_iter()
209                .map(|(k, v)| (k.into(), v.into()))
210                .collect(),
211        )
212    }
213}
214
215impl From<serde_json::Value> for Value {
216    fn from(json: serde_json::Value) -> Self {
217        match json {
218            serde_json::Value::Null => Value::Null,
219            serde_json::Value::Bool(b) => Value::Bool(b),
220            serde_json::Value::Number(n) => {
221                if let Some(i) = n.as_i64() {
222                    Value::Int(i)
223                } else if let Some(f) = n.as_f64() {
224                    Value::Float(f)
225                } else {
226                    Value::Null
227                }
228            }
229            serde_json::Value::String(s) => Value::String(s),
230            serde_json::Value::Array(arr) => {
231                Value::Array(arr.into_iter().map(Value::from).collect())
232            }
233            serde_json::Value::Object(obj) => {
234                Value::Object(obj.into_iter().map(|(k, v)| (k, Value::from(v))).collect())
235            }
236        }
237    }
238}
239
240impl Value {
241    /// Convert from a serde_json::Value
242    pub fn from_json_value(json: serde_json::Value) -> Self {
243        Self::from(json)
244    }
245}
246
247pub mod decimal_serde {
248    use rust_decimal::Decimal;
249    use serde::{self, Deserialize, Deserializer, Serializer};
250
251    pub fn serialize<S>(decimal: &Decimal, serializer: S) -> Result<S::Ok, S::Error>
252    where
253        S: Serializer,
254    {
255        serializer.serialize_str(&decimal.to_string())
256    }
257
258    pub fn deserialize<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
259    where
260        D: Deserializer<'de>,
261    {
262        let s = String::deserialize(deserializer)?;
263        s.parse().map_err(serde::de::Error::custom)
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    #[test]
272    fn test_value_conversions() {
273        assert_eq!(Value::from(true), Value::Bool(true));
274        assert_eq!(Value::from(false), Value::Bool(false));
275        assert_eq!(Value::from(42i64), Value::Int(42));
276        assert_eq!(Value::from(42i32), Value::Int(42));
277        assert_eq!(Value::from(3.14f64), Value::Float(3.14));
278        assert_eq!(Value::from(3.14f32), Value::Float(3.14f32 as f64));
279        assert_eq!(Value::from("hello"), Value::String("hello".to_string()));
280        assert_eq!(
281            Value::from("world".to_string()),
282            Value::String("world".to_string())
283        );
284    }
285
286    #[test]
287    fn test_value_accessors() {
288        assert_eq!(Value::Bool(true).as_bool(), Some(true));
289        assert_eq!(Value::Bool(false).as_bool(), Some(false));
290        assert_eq!(Value::Int(42).as_int(), Some(42));
291        assert_eq!(Value::Float(3.14).as_float(), Some(3.14));
292        assert_eq!(Value::Int(42).as_float(), Some(42.0)); // int to float
293        assert_eq!(Value::String("hello".to_string()).as_str(), Some("hello"));
294    }
295
296    #[test]
297    fn test_null_check() {
298        assert!(Value::Null.is_null());
299        assert!(!Value::Bool(false).is_null());
300        assert!(!Value::Int(0).is_null());
301    }
302
303    #[test]
304    fn test_default() {
305        let val: Value = Default::default();
306        assert!(val.is_null());
307    }
308
309    #[test]
310    fn test_array_conversion() {
311        let arr = Value::from(vec![1i64, 2, 3]);
312        assert_eq!(
313            arr.as_array(),
314            Some(&vec![Value::Int(1), Value::Int(2), Value::Int(3)])
315        );
316    }
317
318    #[test]
319    fn test_object_from_iterator() {
320        let obj: Value = vec![("key1", 42i64), ("key2", 99i64)].into_iter().collect();
321        let map = obj.as_object().unwrap();
322        assert_eq!(map.get("key1"), Some(&Value::Int(42)));
323        assert_eq!(map.get("key2"), Some(&Value::Int(99)));
324    }
325
326    #[test]
327    fn test_object_handle_conversion() {
328        let handle = ObjectHandle::new(123);
329        let val = Value::from(handle);
330        assert_eq!(val.as_object_ref(), Some(ObjectHandle::new(123)));
331    }
332
333    #[test]
334    fn test_decimal_conversion() {
335        let d = Decimal::new(314, 2); // 3.14
336        let val = Value::from(d);
337        assert_eq!(val.as_decimal(), Some(Decimal::new(314, 2)));
338    }
339
340    #[test]
341    fn test_json_serialization_primitives() {
342        assert_eq!(serde_json::to_string(&Value::Null).unwrap(), "null");
343        assert_eq!(serde_json::to_string(&Value::Bool(true)).unwrap(), "true");
344        assert_eq!(serde_json::to_string(&Value::Int(42)).unwrap(), "42");
345        assert_eq!(serde_json::to_string(&Value::Float(3.14)).unwrap(), "3.14");
346        assert_eq!(
347            serde_json::to_string(&Value::String("hello".to_string())).unwrap(),
348            "\"hello\""
349        );
350    }
351
352    #[test]
353    fn test_json_serialization_array() {
354        let arr = Value::Array(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
355        assert_eq!(serde_json::to_string(&arr).unwrap(), "[1,2,3]");
356    }
357
358    #[test]
359    fn test_json_serialization_object() {
360        let mut map = HashMap::new();
361        map.insert("a".to_string(), Value::Int(1));
362        let obj = Value::Object(map);
363        assert_eq!(serde_json::to_string(&obj).unwrap(), "{\"a\":1}");
364    }
365
366    #[test]
367    fn test_json_deserialization() {
368        assert_eq!(serde_json::from_str::<Value>("null").unwrap(), Value::Null);
369        assert_eq!(
370            serde_json::from_str::<Value>("true").unwrap(),
371            Value::Bool(true)
372        );
373        assert_eq!(serde_json::from_str::<Value>("42").unwrap(), Value::Int(42));
374        assert_eq!(
375            serde_json::from_str::<Value>("\"hello\"").unwrap(),
376            Value::String("hello".to_string())
377        );
378    }
379
380    #[test]
381    fn test_mutable_accessors() {
382        let mut arr = Value::Array(vec![Value::Int(1)]);
383        arr.as_array_mut().unwrap().push(Value::Int(2));
384        assert_eq!(arr.as_array().unwrap().len(), 2);
385
386        let mut obj: Value = vec![("key", 1i64)].into_iter().collect();
387        obj.as_object_mut()
388            .unwrap()
389            .insert("key2".to_string(), Value::Int(2));
390        assert_eq!(obj.as_object().unwrap().len(), 2);
391    }
392
393    #[test]
394    fn test_accessor_returns_none_for_wrong_type() {
395        let val = Value::Int(42);
396        assert_eq!(val.as_bool(), None);
397        assert_eq!(val.as_str(), None);
398        assert_eq!(val.as_array(), None);
399        assert_eq!(val.as_object(), None);
400        assert_eq!(val.as_object_ref(), None);
401    }
402}