fusabi_host/
convert.rs

1//! Value conversion traits and helpers.
2//!
3//! This module provides traits and implementations for converting between
4//! Fusabi [`Value`] and Rust types, including serde support when enabled.
5
6use std::collections::HashMap;
7use thiserror::Error;
8
9use crate::value::{Value, ValueType};
10
11/// Error that occurs during value conversion.
12#[derive(Error, Debug, Clone, PartialEq)]
13pub enum ValueConversionError {
14    /// Type mismatch during conversion.
15    #[error("type mismatch: expected {expected}, got {actual}")]
16    TypeMismatch {
17        /// The expected type.
18        expected: ValueType,
19        /// The actual type.
20        actual: ValueType,
21    },
22
23    /// Missing required field in map.
24    #[error("missing field: {0}")]
25    MissingField(String),
26
27    /// Invalid value for the target type.
28    #[error("invalid value: {0}")]
29    InvalidValue(String),
30
31    /// Out of range for numeric conversion.
32    #[error("value out of range: {0}")]
33    OutOfRange(String),
34
35    /// Custom conversion error.
36    #[error("{0}")]
37    Custom(String),
38}
39
40impl ValueConversionError {
41    /// Create a type mismatch error.
42    pub fn type_mismatch(expected: ValueType, actual: ValueType) -> Self {
43        Self::TypeMismatch { expected, actual }
44    }
45
46    /// Create a missing field error.
47    pub fn missing_field(field: impl Into<String>) -> Self {
48        Self::MissingField(field.into())
49    }
50
51    /// Create an invalid value error.
52    pub fn invalid_value(msg: impl Into<String>) -> Self {
53        Self::InvalidValue(msg.into())
54    }
55
56    /// Create an out of range error.
57    pub fn out_of_range(msg: impl Into<String>) -> Self {
58        Self::OutOfRange(msg.into())
59    }
60
61    /// Create a custom error.
62    pub fn custom(msg: impl Into<String>) -> Self {
63        Self::Custom(msg.into())
64    }
65}
66
67/// Trait for types that can be created from a [`Value`].
68pub trait FromValue: Sized {
69    /// Convert from a Value, returning an error if conversion fails.
70    fn from_value(value: Value) -> Result<Self, ValueConversionError>;
71
72    /// Convert from a Value reference, cloning as needed.
73    fn from_value_ref(value: &Value) -> Result<Self, ValueConversionError> {
74        Self::from_value(value.clone())
75    }
76}
77
78/// Trait for types that can be converted into a [`Value`].
79pub trait IntoValue {
80    /// Convert into a Value.
81    fn into_value(self) -> Value;
82}
83
84// Blanket implementation for types that implement Into<Value>
85impl<T: Into<Value>> IntoValue for T {
86    fn into_value(self) -> Value {
87        self.into()
88    }
89}
90
91// FromValue implementations for primitive types
92
93impl FromValue for Value {
94    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
95        Ok(value)
96    }
97}
98
99impl FromValue for () {
100    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
101        match value {
102            Value::Null => Ok(()),
103            _ => Err(ValueConversionError::type_mismatch(
104                ValueType::Null,
105                value.value_type(),
106            )),
107        }
108    }
109}
110
111impl FromValue for bool {
112    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
113        match value {
114            Value::Bool(b) => Ok(b),
115            _ => Err(ValueConversionError::type_mismatch(
116                ValueType::Bool,
117                value.value_type(),
118            )),
119        }
120    }
121}
122
123impl FromValue for i64 {
124    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
125        match value {
126            Value::Int(i) => Ok(i),
127            _ => Err(ValueConversionError::type_mismatch(
128                ValueType::Int,
129                value.value_type(),
130            )),
131        }
132    }
133}
134
135impl FromValue for i32 {
136    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
137        match value {
138            Value::Int(i) => i.try_into().map_err(|_| {
139                ValueConversionError::out_of_range(format!(
140                    "{} is out of range for i32",
141                    i
142                ))
143            }),
144            _ => Err(ValueConversionError::type_mismatch(
145                ValueType::Int,
146                value.value_type(),
147            )),
148        }
149    }
150}
151
152impl FromValue for u64 {
153    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
154        match value {
155            Value::Int(i) => i.try_into().map_err(|_| {
156                ValueConversionError::out_of_range(format!(
157                    "{} is out of range for u64",
158                    i
159                ))
160            }),
161            _ => Err(ValueConversionError::type_mismatch(
162                ValueType::Int,
163                value.value_type(),
164            )),
165        }
166    }
167}
168
169impl FromValue for u32 {
170    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
171        match value {
172            Value::Int(i) => i.try_into().map_err(|_| {
173                ValueConversionError::out_of_range(format!(
174                    "{} is out of range for u32",
175                    i
176                ))
177            }),
178            _ => Err(ValueConversionError::type_mismatch(
179                ValueType::Int,
180                value.value_type(),
181            )),
182        }
183    }
184}
185
186impl FromValue for usize {
187    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
188        match value {
189            Value::Int(i) => i.try_into().map_err(|_| {
190                ValueConversionError::out_of_range(format!(
191                    "{} is out of range for usize",
192                    i
193                ))
194            }),
195            _ => Err(ValueConversionError::type_mismatch(
196                ValueType::Int,
197                value.value_type(),
198            )),
199        }
200    }
201}
202
203impl FromValue for f64 {
204    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
205        match value {
206            Value::Float(f) => Ok(f),
207            Value::Int(i) => Ok(i as f64),
208            _ => Err(ValueConversionError::type_mismatch(
209                ValueType::Float,
210                value.value_type(),
211            )),
212        }
213    }
214}
215
216impl FromValue for f32 {
217    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
218        match value {
219            Value::Float(f) => Ok(f as f32),
220            Value::Int(i) => Ok(i as f32),
221            _ => Err(ValueConversionError::type_mismatch(
222                ValueType::Float,
223                value.value_type(),
224            )),
225        }
226    }
227}
228
229impl FromValue for String {
230    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
231        match value {
232            Value::String(s) => Ok(s),
233            _ => Err(ValueConversionError::type_mismatch(
234                ValueType::String,
235                value.value_type(),
236            )),
237        }
238    }
239}
240
241impl<T: FromValue> FromValue for Vec<T> {
242    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
243        match value {
244            Value::List(list) => list.into_iter().map(T::from_value).collect(),
245            _ => Err(ValueConversionError::type_mismatch(
246                ValueType::List,
247                value.value_type(),
248            )),
249        }
250    }
251}
252
253impl<T: FromValue> FromValue for HashMap<String, T> {
254    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
255        match value {
256            Value::Map(map) => map
257                .into_iter()
258                .map(|(k, v)| T::from_value(v).map(|v| (k, v)))
259                .collect(),
260            _ => Err(ValueConversionError::type_mismatch(
261                ValueType::Map,
262                value.value_type(),
263            )),
264        }
265    }
266}
267
268impl<T: FromValue> FromValue for Option<T> {
269    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
270        match value {
271            Value::Null => Ok(None),
272            other => T::from_value(other).map(Some),
273        }
274    }
275}
276
277impl FromValue for Vec<u8> {
278    fn from_value(value: Value) -> Result<Self, ValueConversionError> {
279        match value {
280            Value::Bytes(b) => Ok(b),
281            _ => Err(ValueConversionError::type_mismatch(
282                ValueType::Bytes,
283                value.value_type(),
284            )),
285        }
286    }
287}
288
289// Additional Into<Value> implementations for common types
290
291impl From<()> for Value {
292    fn from(_: ()) -> Self {
293        Value::Null
294    }
295}
296
297impl From<usize> for Value {
298    fn from(u: usize) -> Self {
299        Value::Int(u as i64)
300    }
301}
302
303impl From<u64> for Value {
304    fn from(u: u64) -> Self {
305        Value::Int(u as i64)
306    }
307}
308
309impl From<u32> for Value {
310    fn from(u: u32) -> Self {
311        Value::Int(u as i64)
312    }
313}
314
315impl<T: IntoValue> From<Vec<T>> for Value {
316    fn from(v: Vec<T>) -> Self {
317        Value::List(v.into_iter().map(|x| x.into_value()).collect())
318    }
319}
320
321// Serde integration when feature is enabled
322#[cfg(feature = "serde-support")]
323mod serde_support {
324    use super::*;
325    use serde::{de::DeserializeOwned, Serialize};
326    use serde_json;
327
328    /// Convert a Value to a JSON value.
329    pub fn value_to_json(value: &Value) -> serde_json::Value {
330        match value {
331            Value::Null => serde_json::Value::Null,
332            Value::Bool(b) => serde_json::Value::Bool(*b),
333            Value::Int(i) => serde_json::Value::Number((*i).into()),
334            Value::Float(f) => {
335                serde_json::Number::from_f64(*f)
336                    .map(serde_json::Value::Number)
337                    .unwrap_or(serde_json::Value::Null)
338            }
339            Value::String(s) => serde_json::Value::String(s.clone()),
340            Value::List(l) => {
341                serde_json::Value::Array(l.iter().map(value_to_json).collect())
342            }
343            Value::Map(m) => {
344                let obj: serde_json::Map<String, serde_json::Value> = m
345                    .iter()
346                    .map(|(k, v)| (k.clone(), value_to_json(v)))
347                    .collect();
348                serde_json::Value::Object(obj)
349            }
350            Value::Function(_) => serde_json::Value::Null,
351            Value::Bytes(b) => {
352                use base64::Engine as _;
353                let encoded = base64::engine::general_purpose::STANDARD.encode(b);
354                serde_json::Value::String(encoded)
355            }
356            Value::Error(e) => {
357                let mut obj = serde_json::Map::new();
358                obj.insert("error".into(), serde_json::Value::String(e.clone()));
359                serde_json::Value::Object(obj)
360            }
361        }
362    }
363
364    /// Convert a JSON value to a Value.
365    pub fn json_to_value(json: serde_json::Value) -> Value {
366        match json {
367            serde_json::Value::Null => Value::Null,
368            serde_json::Value::Bool(b) => Value::Bool(b),
369            serde_json::Value::Number(n) => {
370                if let Some(i) = n.as_i64() {
371                    Value::Int(i)
372                } else if let Some(f) = n.as_f64() {
373                    Value::Float(f)
374                } else {
375                    Value::Null
376                }
377            }
378            serde_json::Value::String(s) => Value::String(s),
379            serde_json::Value::Array(arr) => {
380                Value::List(arr.into_iter().map(json_to_value).collect())
381            }
382            serde_json::Value::Object(obj) => {
383                let map: HashMap<String, Value> = obj
384                    .into_iter()
385                    .map(|(k, v)| (k, json_to_value(v)))
386                    .collect();
387                Value::Map(map)
388            }
389        }
390    }
391
392    /// Deserialize a Value into a type implementing DeserializeOwned.
393    pub fn from_value_serde<T: DeserializeOwned>(
394        value: Value,
395    ) -> Result<T, ValueConversionError> {
396        let json = value_to_json(&value);
397        serde_json::from_value(json)
398            .map_err(|e| ValueConversionError::custom(e.to_string()))
399    }
400
401    /// Serialize a type implementing Serialize into a Value.
402    pub fn to_value_serde<T: Serialize>(value: &T) -> Result<Value, ValueConversionError> {
403        let json = serde_json::to_value(value)
404            .map_err(|e| ValueConversionError::custom(e.to_string()))?;
405        Ok(json_to_value(json))
406    }
407
408    impl Value {
409        /// Deserialize this Value into a serde-compatible type.
410        pub fn deserialize<T: DeserializeOwned>(self) -> Result<T, ValueConversionError> {
411            from_value_serde(self)
412        }
413
414        /// Convert to JSON string.
415        pub fn to_json_string(&self) -> String {
416            let json = value_to_json(self);
417            serde_json::to_string(&json).unwrap_or_else(|_| "null".to_string())
418        }
419
420        /// Convert to pretty JSON string.
421        pub fn to_json_string_pretty(&self) -> String {
422            let json = value_to_json(self);
423            serde_json::to_string_pretty(&json).unwrap_or_else(|_| "null".to_string())
424        }
425
426        /// Parse from JSON string.
427        pub fn from_json_str(s: &str) -> Result<Self, ValueConversionError> {
428            let json: serde_json::Value = serde_json::from_str(s)
429                .map_err(|e| ValueConversionError::custom(e.to_string()))?;
430            Ok(json_to_value(json))
431        }
432    }
433}
434
435#[cfg(feature = "serde-support")]
436pub use serde_support::*;
437
438/// Helper macro to extract a required field from a map Value.
439#[macro_export]
440macro_rules! extract_field {
441    ($map:expr, $field:expr, $type:ty) => {{
442        let map = match $map {
443            $crate::Value::Map(m) => m,
444            other => {
445                return Err($crate::ValueConversionError::type_mismatch(
446                    $crate::ValueType::Map,
447                    other.value_type(),
448                ))
449            }
450        };
451        let value = map
452            .get($field)
453            .ok_or_else(|| $crate::ValueConversionError::missing_field($field))?
454            .clone();
455        <$type as $crate::FromValue>::from_value(value)?
456    }};
457}
458
459/// Helper macro to extract an optional field from a map Value.
460#[macro_export]
461macro_rules! extract_field_opt {
462    ($map:expr, $field:expr, $type:ty) => {{
463        let map = match $map {
464            $crate::Value::Map(ref m) => m,
465            other => {
466                return Err($crate::ValueConversionError::type_mismatch(
467                    $crate::ValueType::Map,
468                    other.value_type(),
469                ))
470            }
471        };
472        match map.get($field) {
473            Some(v) => Some(<$type as $crate::FromValue>::from_value(v.clone())?),
474            None => None,
475        }
476    }};
477}
478
479#[cfg(test)]
480mod tests {
481    use super::*;
482
483    #[test]
484    fn test_from_value_primitives() {
485        assert_eq!(bool::from_value(Value::Bool(true)).unwrap(), true);
486        assert_eq!(i64::from_value(Value::Int(42)).unwrap(), 42);
487        assert_eq!(f64::from_value(Value::Float(3.14)).unwrap(), 3.14);
488        assert_eq!(
489            String::from_value(Value::String("hello".into())).unwrap(),
490            "hello"
491        );
492    }
493
494    #[test]
495    fn test_from_value_type_mismatch() {
496        let err = bool::from_value(Value::Int(42)).unwrap_err();
497        assert!(matches!(err, ValueConversionError::TypeMismatch { .. }));
498    }
499
500    #[test]
501    fn test_from_value_collections() {
502        let list = Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
503        let vec: Vec<i64> = Vec::from_value(list).unwrap();
504        assert_eq!(vec, vec![1, 2, 3]);
505
506        let mut map = HashMap::new();
507        map.insert("a".into(), Value::Int(1));
508        map.insert("b".into(), Value::Int(2));
509        let value = Value::Map(map);
510        let result: HashMap<String, i64> = HashMap::from_value(value).unwrap();
511        assert_eq!(result.get("a"), Some(&1));
512        assert_eq!(result.get("b"), Some(&2));
513    }
514
515    #[test]
516    fn test_from_value_option() {
517        let opt: Option<i64> = Option::from_value(Value::Null).unwrap();
518        assert_eq!(opt, None);
519
520        let opt: Option<i64> = Option::from_value(Value::Int(42)).unwrap();
521        assert_eq!(opt, Some(42));
522    }
523
524    #[test]
525    fn test_numeric_range() {
526        let err = i32::from_value(Value::Int(i64::MAX)).unwrap_err();
527        assert!(matches!(err, ValueConversionError::OutOfRange(_)));
528    }
529
530    #[cfg(feature = "serde-support")]
531    mod serde_tests {
532        use super::*;
533        use serde::{Deserialize, Serialize};
534
535        #[derive(Debug, PartialEq, Serialize, Deserialize)]
536        struct TestStruct {
537            name: String,
538            value: i32,
539            optional: Option<String>,
540        }
541
542        #[test]
543        fn test_serde_roundtrip() {
544            let original = TestStruct {
545                name: "test".into(),
546                value: 42,
547                optional: Some("opt".into()),
548            };
549
550            let value = to_value_serde(&original).unwrap();
551            let restored: TestStruct = from_value_serde(value).unwrap();
552            assert_eq!(original, restored);
553        }
554
555        #[test]
556        fn test_json_conversion() {
557            let value = Value::Map({
558                let mut m = HashMap::new();
559                m.insert("key".into(), Value::String("value".into()));
560                m.insert("number".into(), Value::Int(42));
561                m
562            });
563
564            let json_str = value.to_json_string();
565            let parsed = Value::from_json_str(&json_str).unwrap();
566
567            // Map ordering may differ, so check individual fields
568            let parsed_map = parsed.as_map().unwrap();
569            assert_eq!(
570                parsed_map.get("key"),
571                Some(&Value::String("value".into()))
572            );
573            assert_eq!(parsed_map.get("number"), Some(&Value::Int(42)));
574        }
575    }
576}