Skip to main content

frp_plexus/
value.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::types::TypeSig;
6
7// ---------------------------------------------------------------------------
8// Value
9// ---------------------------------------------------------------------------
10
11/// A runtime-typed value that can flow through ports in an frp graph.
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub enum Value {
14    /// The absence of a value.
15    Null,
16    /// A boolean value.
17    Bool(bool),
18    /// A 64-bit signed integer.
19    Int(i64),
20    /// A 64-bit floating-point number.
21    Float(f64),
22    /// A UTF-8 string.
23    Str(std::string::String),
24    /// Raw bytes.
25    Bytes(Vec<u8>),
26    /// An ordered, homogeneous-or-heterogeneous list.
27    List(Vec<Value>),
28    /// A string-keyed map of values.
29    Map(BTreeMap<std::string::String, Value>),
30}
31
32impl Value {
33    /// Return the [`TypeSig`] that describes this value's runtime type.
34    pub fn type_sig(&self) -> TypeSig {
35        match self {
36            Value::Null => TypeSig::Null,
37            Value::Bool(_) => TypeSig::Bool,
38            Value::Int(_) => TypeSig::Int,
39            Value::Float(_) => TypeSig::Float,
40            Value::Str(_) => TypeSig::String,
41            Value::Bytes(_) => TypeSig::Bytes,
42            Value::List(items) => {
43                // Infer element type from first item; fall back to Any for empty lists.
44                let elem_sig = items.first().map(|v| v.type_sig()).unwrap_or(TypeSig::Any);
45                TypeSig::List(Box::new(elem_sig))
46            }
47            Value::Map(map) => {
48                let val_sig = map
49                    .values()
50                    .next()
51                    .map(|v| v.type_sig())
52                    .unwrap_or(TypeSig::Any);
53                TypeSig::Map(Box::new(val_sig))
54            }
55        }
56    }
57
58    /// Returns `true` if this value is `Value::Null`.
59    #[inline]
60    pub fn is_null(&self) -> bool {
61        matches!(self, Value::Null)
62    }
63
64    /// Attempt to get the inner `bool`.
65    pub fn as_bool(&self) -> Option<bool> {
66        match self {
67            Value::Bool(b) => Some(*b),
68            _ => None,
69        }
70    }
71
72    /// Attempt to get the inner `i64`.
73    pub fn as_int(&self) -> Option<i64> {
74        match self {
75            Value::Int(i) => Some(*i),
76            _ => None,
77        }
78    }
79
80    /// Attempt to get the inner `f64`.
81    pub fn as_float(&self) -> Option<f64> {
82        match self {
83            Value::Float(f) => Some(*f),
84            _ => None,
85        }
86    }
87
88    /// Attempt to get the inner string slice.
89    pub fn as_str(&self) -> Option<&str> {
90        match self {
91            Value::Str(s) => Some(s.as_str()),
92            _ => None,
93        }
94    }
95
96    /// Attempt to get a reference to the inner list.
97    pub fn as_list(&self) -> Option<&Vec<Value>> {
98        match self {
99            Value::List(v) => Some(v),
100            _ => None,
101        }
102    }
103
104    /// Attempt to get a reference to the inner map.
105    pub fn as_map(&self) -> Option<&BTreeMap<std::string::String, Value>> {
106        match self {
107            Value::Map(m) => Some(m),
108            _ => None,
109        }
110    }
111}
112
113impl std::fmt::Display for Value {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        match self {
116            Value::Null => write!(f, "null"),
117            Value::Bool(b) => write!(f, "{}", b),
118            Value::Int(i) => write!(f, "{}", i),
119            Value::Float(fl) => write!(f, "{}", fl),
120            Value::Str(s) => write!(f, "\"{}\"", s),
121            Value::Bytes(b) => write!(f, "<bytes:{}>", b.len()),
122            Value::List(v) => write!(f, "[..{}]", v.len()),
123            Value::Map(m) => write!(f, "{{..{}}}", m.len()),
124        }
125    }
126}
127
128impl From<bool> for Value {
129    fn from(b: bool) -> Self {
130        Value::Bool(b)
131    }
132}
133
134impl From<i64> for Value {
135    fn from(i: i64) -> Self {
136        Value::Int(i)
137    }
138}
139
140impl From<f64> for Value {
141    fn from(f: f64) -> Self {
142        Value::Float(f)
143    }
144}
145
146impl From<std::string::String> for Value {
147    fn from(s: std::string::String) -> Self {
148        Value::Str(s)
149    }
150}
151
152impl From<&str> for Value {
153    fn from(s: &str) -> Self {
154        Value::Str(s.to_owned())
155    }
156}
157
158// ---------------------------------------------------------------------------
159// Tests
160// ---------------------------------------------------------------------------
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn type_sig_matches_variant() {
168        assert_eq!(Value::Null.type_sig(), TypeSig::Null);
169        assert_eq!(Value::Bool(true).type_sig(), TypeSig::Bool);
170        assert_eq!(Value::Int(1).type_sig(), TypeSig::Int);
171        assert_eq!(Value::Float(1.0).type_sig(), TypeSig::Float);
172        assert_eq!(Value::Str("hi".into()).type_sig(), TypeSig::String);
173    }
174
175    #[test]
176    fn list_type_sig_infers_element() {
177        let v = Value::List(vec![Value::Int(1), Value::Int(2)]);
178        assert_eq!(v.type_sig(), TypeSig::List(Box::new(TypeSig::Int)));
179    }
180
181    #[test]
182    fn empty_list_type_sig_is_any() {
183        let v = Value::List(vec![]);
184        assert_eq!(v.type_sig(), TypeSig::List(Box::new(TypeSig::Any)));
185    }
186
187    #[test]
188    fn from_conversions() {
189        assert_eq!(Value::from(42i64), Value::Int(42));
190        assert_eq!(Value::from("hello"), Value::Str("hello".into()));
191    }
192}