Skip to main content

kui_value/
lib.rs

1//! [`Value`]: the plain-data payload type for everything that crosses an
2//! event or binding boundary.
3//!
4//! A click payload, a drag event's fields, a readback for a script: all of
5//! them are a `Value`, so Rust, Lua, Node and C see one shape. A Rust app
6//! that wants an exhaustive `match` over its own payloads puts a typed
7//! enum on top with [`message`].
8//!
9//! A crate of its own (ADR 0075, decision 3), so what has no kui core —
10//! the data channel, the recording's reader — speaks the same values;
11//! `kui-core` re-exports all of it where it always was (`kui_core::Value`,
12//! `kui_core::json`, `kui_core::message`). Features: `lua`, the
13//! conversions to and from an `mlua` value ([`lua`]); `derive`,
14//! `#[derive(Message)]` from `kui-derive`.
15//!
16//! ```rust
17//! use kui_value::Value;
18//!
19//! let v = Value::map([
20//!     ("kind", Value::str("drag")),
21//!     ("x", Value::from(1.5f32)),
22//!     ("n", Value::from(4usize)),
23//!     ("on", true.into()),
24//! ]);
25//! assert_eq!(v.get_str("kind"), Some("drag"));
26//! assert_eq!(v.get_f32("x"), Some(1.5));
27//! assert_eq!(v.get_float("n"), Some(4.0)); // an int reads as a float
28//! assert_eq!(v.get_str("x"), None); // the wrong type is None
29//! assert_eq!(Value::from("save"), Value::Str("save".into()));
30//! ```
31
32pub mod json;
33#[cfg(feature = "lua")]
34pub mod lua;
35pub mod message;
36
37pub use message::{MessageError, MessageField};
38
39/// `#[derive(Message)]`, with the `derive` feature: a typed message to and
40/// from a payload. Its generated code reaches `kui_native` unless the type
41/// says `#[message(crate = "kui_value")]` (or `kui_core`).
42#[cfg(feature = "derive")]
43pub use kui_derive::Message;
44
45/// A dynamically typed value: null, bool, int, float, string, list or an
46/// ordered string-keyed map. See the [module docs](self) for an example.
47#[derive(Debug, Default, PartialEq)]
48pub enum Value {
49    #[default]
50    Null,
51    Bool(bool),
52    Int(i64),
53    Float(f64),
54    Str(String),
55    List(Vec<Value>),
56    Map(Vec<(String, Value)>),
57}
58
59impl Clone for Value {
60    fn clone(&self) -> Self {
61        match self {
62            Value::Null => Value::Null,
63            Value::Bool(b) => Value::Bool(*b),
64            Value::Int(i) => Value::Int(*i),
65            Value::Float(f) => Value::Float(*f),
66            Value::Str(s) => Value::Str(s.clone()),
67            Value::List(items) => Value::List(items.clone()),
68            Value::Map(entries) => Value::Map(entries.clone()),
69        }
70    }
71
72    /// Into the buffers `self` already has where the two are the same
73    /// shape — a string into a string, a map into a map, entry by entry —
74    /// so a value written over with another like it allocates nothing:
75    /// what the core does with every click message every frame (backlog
76    /// F281). The result is `source.clone()` either way.
77    fn clone_from(&mut self, source: &Self) {
78        match (self, source) {
79            (Value::Str(a), Value::Str(b)) => a.clone_from(b),
80            (Value::List(a), Value::List(b)) => a.clone_from(b),
81            (Value::Map(a), Value::Map(b)) => {
82                // Not `Vec::clone_from`: a tuple's `clone_from` is a fresh
83                // clone, which would allocate every key and value again.
84                a.truncate(b.len());
85                for ((ka, va), (kb, vb)) in a.iter_mut().zip(b) {
86                    ka.clone_from(kb);
87                    va.clone_from(vb);
88                }
89                let have = a.len();
90                a.extend(b[have..].iter().cloned());
91            }
92            (this, source) => *this = source.clone(),
93        }
94    }
95}
96
97impl Value {
98    /// Whether the value holds a heap buffer — a string, a list or a map —
99    /// that [`Clone::clone_from`] can write another like it into.
100    #[inline]
101    pub fn owns_buffers(&self) -> bool {
102        matches!(self, Value::Str(_) | Value::List(_) | Value::Map(_))
103    }
104
105    /// A string value.
106    pub fn str(s: impl Into<String>) -> Value {
107        Value::Str(s.into())
108    }
109
110    /// A map from `(key, value)` pairs, in the order given.
111    pub fn map(entries: impl IntoIterator<Item = (&'static str, Value)>) -> Value {
112        Value::Map(
113            entries
114                .into_iter()
115                .map(|(k, v)| (k.to_string(), v))
116                .collect(),
117        )
118    }
119
120    /// The entry under `key` of a map; `None` for a missing key or a
121    /// value that is not a map.
122    pub fn get(&self, key: &str) -> Option<&Value> {
123        match self {
124            Value::Map(entries) => entries.iter().find(|(k, _)| k == key).map(|(_, v)| v),
125            _ => None,
126        }
127    }
128
129    pub fn as_str(&self) -> Option<&str> {
130        match self {
131            Value::Str(s) => Some(s),
132            _ => None,
133        }
134    }
135
136    /// The number as an integer; a float is truncated.
137    pub fn as_int(&self) -> Option<i64> {
138        match self {
139            Value::Int(i) => Some(*i),
140            Value::Float(f) => Some(*f as i64),
141            _ => None,
142        }
143    }
144
145    pub fn as_list(&self) -> Option<&[Value]> {
146        match self {
147            Value::List(items) => Some(items),
148            _ => None,
149        }
150    }
151
152    /// The number as a float; an int converts.
153    pub fn as_float(&self) -> Option<f64> {
154        match self {
155            Value::Float(f) => Some(*f),
156            Value::Int(i) => Some(*i as f64),
157            _ => None,
158        }
159    }
160
161    /// What kind of value this is, for an error message.
162    pub fn type_name(&self) -> &'static str {
163        match self {
164            Value::Null => "nil",
165            Value::Bool(_) => "boolean",
166            Value::Int(_) | Value::Float(_) => "number",
167            Value::Str(_) => "string",
168            Value::List(_) => "list",
169            Value::Map(_) => "map",
170        }
171    }
172
173    pub fn as_bool(&self) -> Option<bool> {
174        match self {
175            Value::Bool(b) => Some(*b),
176            _ => None,
177        }
178    }
179
180    /// `get(key)` then `as_str`: a map entry read as a string.
181    #[inline]
182    pub fn get_str(&self, key: &str) -> Option<&str> {
183        self.get(key).and_then(Value::as_str)
184    }
185
186    /// `get(key)` then `as_int`.
187    #[inline]
188    pub fn get_int(&self, key: &str) -> Option<i64> {
189        self.get(key).and_then(Value::as_int)
190    }
191
192    /// `get(key)` then `as_float`.
193    #[inline]
194    pub fn get_float(&self, key: &str) -> Option<f64> {
195        self.get(key).and_then(Value::as_float)
196    }
197
198    /// `get(key)` then `as_float`, as the `f32` geometry is in — a drag's
199    /// `x`, a scroll's `dy`.
200    #[inline]
201    pub fn get_f32(&self, key: &str) -> Option<f32> {
202        self.get_float(key).map(|v| v as f32)
203    }
204
205    /// `get(key)` then `as_bool`.
206    #[inline]
207    pub fn get_bool(&self, key: &str) -> Option<bool> {
208        self.get(key).and_then(Value::as_bool)
209    }
210}
211
212impl Value {
213    /// A float, as a readback spells one.
214    pub fn float(v: f32) -> Value {
215        Value::Float(v as f64)
216    }
217
218    /// An `Option` as the value or `Null`: a readback keeps every key,
219    /// so a reader destructures a stable shape.
220    pub fn opt<T>(v: Option<T>, f: impl FnOnce(T) -> Value) -> Value {
221        v.map_or(Value::Null, f)
222    }
223
224    pub fn opt_str(s: &Option<String>) -> Value {
225        Value::opt(s.as_ref(), |s| Value::Str(s.clone()))
226    }
227
228    pub fn opt_float(v: Option<f32>) -> Value {
229        Value::opt(v, Value::float)
230    }
231
232    pub fn opt_usize(v: Option<usize>) -> Value {
233        Value::opt(v, |v| Value::Int(v as i64))
234    }
235
236    pub fn opt_bool(v: Option<bool>) -> Value {
237        Value::opt(v, Value::Bool)
238    }
239
240    pub fn list(items: impl IntoIterator<Item = Value>) -> Value {
241        Value::List(items.into_iter().collect())
242    }
243
244    pub fn floats(items: &[f32]) -> Value {
245        Value::list(items.iter().map(|v| Value::float(*v)))
246    }
247}
248
249/// A string message read back, so `ev.message::<String>()` is a bare
250/// `on_click("save")`'s `"save"`: the core keeps a non-map message under
251/// `tag` (ADR 0047), and `message` reads from there.
252impl TryFrom<&Value> for String {
253    type Error = ();
254
255    fn try_from(v: &Value) -> Result<Self, ()> {
256        v.as_str().map(str::to_string).ok_or(())
257    }
258}
259
260/// A copy, so `ev.message::<Value>()` and `ev.split::<Value>()` read an
261/// event's message untyped.
262impl From<&Value> for Value {
263    fn from(v: &Value) -> Self {
264        v.clone()
265    }
266}
267
268impl From<&str> for Value {
269    fn from(s: &str) -> Self {
270        Value::Str(s.to_string())
271    }
272}
273
274impl From<String> for Value {
275    fn from(s: String) -> Self {
276        Value::Str(s)
277    }
278}
279
280impl From<i64> for Value {
281    fn from(v: i64) -> Self {
282        Value::Int(v)
283    }
284}
285
286impl From<i32> for Value {
287    fn from(v: i32) -> Self {
288        Value::Int(v.into())
289    }
290}
291
292impl From<u32> for Value {
293    fn from(v: u32) -> Self {
294        Value::Int(v.into())
295    }
296}
297
298/// An index or a count. One past `i64::MAX` cannot be a payload's number,
299/// so it saturates there rather than wrapping negative.
300impl From<usize> for Value {
301    fn from(v: usize) -> Self {
302        Value::Int(i64::try_from(v).unwrap_or(i64::MAX))
303    }
304}
305
306impl From<f32> for Value {
307    fn from(v: f32) -> Self {
308        Value::float(v)
309    }
310}
311
312impl From<f64> for Value {
313    fn from(v: f64) -> Self {
314        Value::Float(v)
315    }
316}
317
318impl From<bool> for Value {
319    fn from(v: bool) -> Self {
320        Value::Bool(v)
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn map_get_finds_entries() {
330        let v = Value::map([("kind", Value::str("inc")), ("by", Value::Int(2))]);
331        assert_eq!(v.get("kind").and_then(Value::as_str), Some("inc"));
332        assert_eq!(v.get("by").and_then(Value::as_int), Some(2));
333        assert_eq!(v.get("missing"), None);
334    }
335
336    #[test]
337    fn clone_from_is_a_clone_whatever_it_overwrites() {
338        let shapes = [
339            Value::Null,
340            Value::Bool(true),
341            Value::Int(-3),
342            Value::Float(2.5),
343            Value::str("row"),
344            Value::str(""),
345            Value::List(vec![Value::Int(1), Value::str("two")]),
346            Value::List(Vec::new()),
347            Value::map([("kind", Value::str("row")), ("line", Value::Int(7))]),
348            Value::map([
349                ("kind", Value::str("a longer kind")),
350                ("x", Value::Float(1.0)),
351            ]),
352            Value::map([("only", Value::Null)]),
353            Value::map([
354                ("kind", Value::str("row")),
355                ("line", Value::Int(7)),
356                (
357                    "nested",
358                    Value::map([("deep", Value::List(vec![Value::Bool(false)]))]),
359                ),
360            ]),
361        ];
362        for into in &shapes {
363            for from in &shapes {
364                let mut v = into.clone();
365                v.clone_from(from);
366                assert_eq!(&v, from, "{from:?} written over {into:?}");
367            }
368        }
369    }
370
371    #[test]
372    fn clone_from_keeps_the_buffers_of_a_value_like_it() {
373        let mut v = Value::map([("kind", Value::str("row")), ("line", Value::Int(1))]);
374        let Value::Map(entries) = &v else {
375            unreachable!()
376        };
377        let (keys, text) = (entries.as_ptr(), entries[0].0.as_ptr());
378        v.clone_from(&Value::map([
379            ("kind", Value::str("cut")),
380            ("line", Value::Int(2)),
381        ]));
382        let Value::Map(entries) = &v else {
383            unreachable!()
384        };
385        assert_eq!((entries.as_ptr(), entries[0].0.as_ptr()), (keys, text));
386        assert_eq!(v.get_str("kind"), Some("cut"));
387    }
388
389    #[test]
390    fn numeric_coercions() {
391        assert_eq!(Value::Int(3).as_float(), Some(3.0));
392        assert_eq!(Value::Float(3.7).as_int(), Some(3));
393        assert_eq!(Value::str("x").as_int(), None);
394    }
395}