Skip to main content

whisker_runtime/
value.rs

1//! `WhiskerValue` — the universal tagged-union value model shared by
2//! the whole framework.
3//!
4//! It is the wire representation for everything that crosses the
5//! Rust ⇄ native boundary as data rather than a handle:
6//!
7//!   - **module function args / returns** (`ElementRef::invoke`,
8//!     `PlatformModule::invoke`) — Case ② raw values, no typed-arg
9//!     deserialization at the boundary, and
10//!   - **event payloads** — the body Lynx hands a tap / touch /
11//!     animation handler, delivered to the Rust closure as a
12//!     recursive `WhiskerValue` tree.
13//!
14//! Lives in `whisker-runtime` (not `whisker-driver`) because the
15//! [`DynRenderer`](crate::view::renderer::DynRenderer) event-listener
16//! trait — defined here — needs to name the payload type, and
17//! `whisker-driver` depends on `whisker-runtime`, not the reverse.
18//! The FFI mirror (`WhiskerValueRaw`) and the
19//! `WhiskerValueRaw` ⇄ `WhiskerValue` marshalling stay in
20//! `whisker-driver-sys` / `whisker-driver` — this module is pure
21//! Rust with no FFI.
22//!
23//! ## Typed extraction
24//!
25//! [`WhiskerValue::deserialize_into`] converts a value tree into any
26//! `serde::Deserialize` type (the typed event structs in
27//! [`crate::event`] use this). The conversion goes through
28//! `serde_json::Value` as a mature, well-tested `Deserializer`
29//! rather than hand-rolling one over `WhiskerValue` — the
30//! intermediate is in-memory (no string parse), so the binary-wire
31//! win over the old JSON-string event payload is preserved.
32
33use std::collections::BTreeMap;
34use std::fmt;
35
36use serde::de::{DeserializeOwned, Deserializer, MapAccess, SeqAccess, Visitor};
37use serde::Deserialize;
38
39/// Tagged-union variant set passed between Rust and the platform
40/// side — module args/returns and event payloads alike.
41///
42/// `String`/`Bytes`/`Array`/`Map` are Rust-allocated and dropped on
43/// scope exit. Conversion to/from the C `WhiskerValueRaw` form
44/// happens at the FFI boundary in `whisker-driver`.
45///
46/// This enum is intentionally **closed** (not `#[non_exhaustive]`): it
47/// models a sealed tagged union mirroring the C `WhiskerValueRaw`
48/// discriminator, and the FFI encoder in `whisker-driver` must handle
49/// every variant exhaustively. Adding a variant is a deliberate,
50/// breaking wire-format change touching both sides of the boundary, so
51/// callers may rely on matching it exhaustively.
52#[derive(Debug, Clone, PartialEq, Default)]
53pub enum WhiskerValue {
54    #[default]
55    Null,
56    Bool(bool),
57    Int(i64),
58    Float(f64),
59    String(String),
60    Bytes(Vec<u8>),
61    Array(Vec<WhiskerValue>),
62    /// String-keyed map. `BTreeMap` for deterministic iteration
63    /// order — important for snapshot tests and the proc-macro
64    /// layer that codegen's destructuring patterns based on
65    /// key order.
66    Map(BTreeMap<String, WhiskerValue>),
67    /// Bridge / platform reported failure. Carries a UTF-8
68    /// description; the proc macro lifts this into a typed
69    /// `Result::Err`.
70    Error(String),
71}
72
73impl WhiskerValue {
74    /// Convenience constructor that wraps a `BTreeMap` literal.
75    /// Lets test code write
76    /// `WhiskerValue::map([("k", WhiskerValue::Int(1))])` without
77    /// importing `BTreeMap`.
78    pub fn map<I, K>(entries: I) -> Self
79    where
80        I: IntoIterator<Item = (K, WhiskerValue)>,
81        K: Into<String>,
82    {
83        let mut m = BTreeMap::new();
84        for (k, v) in entries {
85            m.insert(k.into(), v);
86        }
87        WhiskerValue::Map(m)
88    }
89
90    /// Build the `{ "args": [ … ] }` params object that Whisker module
91    /// element methods (`@WhiskerUIMethod`) decode — their forwarders
92    /// read positional arguments from `params.args`. Built-in Lynx
93    /// methods read named fields directly and use [`map`](Self::map)
94    /// instead; this helper is for module-element handles like
95    /// `VideoHandle` invoking through the unified `ElementRef::invoke`.
96    pub fn args<I>(items: I) -> Self
97    where
98        I: IntoIterator<Item = WhiskerValue>,
99    {
100        WhiskerValue::map([("args", WhiskerValue::Array(items.into_iter().collect()))])
101    }
102
103    /// Returns the Error message if `self` is the Error variant.
104    /// Convenience for callers that want to bail on any failure
105    /// without matching the variant manually.
106    pub fn as_error(&self) -> Option<&str> {
107        if let WhiskerValue::Error(msg) = self {
108            Some(msg.as_str())
109        } else {
110            None
111        }
112    }
113
114    /// Deserialize this value tree into a typed `T`.
115    ///
116    /// Used by the typed-event layer: a `Fn(TouchEvent)` handler
117    /// receives the event body as a `WhiskerValue`, then
118    /// `deserialize_into::<TouchEvent>()` recovers the struct. On
119    /// failure (shape mismatch, missing required field) returns
120    /// `Err` with a human-readable message — the event binding logs
121    /// it together with the raw value rather than silently dropping
122    /// the handler call.
123    pub fn deserialize_into<T: DeserializeOwned>(&self) -> Result<T, String> {
124        serde_json::from_value(self.to_json()).map_err(|e| e.to_string())
125    }
126
127    /// Lower this value tree into a `serde_json::Value`. In-memory
128    /// only — the target of [`deserialize_into`](Self::deserialize_into).
129    pub fn to_json(&self) -> serde_json::Value {
130        use serde_json::Value as J;
131        match self {
132            WhiskerValue::Null => J::Null,
133            WhiskerValue::Bool(b) => J::Bool(*b),
134            WhiskerValue::Int(i) => J::Number((*i).into()),
135            WhiskerValue::Float(f) => serde_json::Number::from_f64(*f)
136                .map(J::Number)
137                .unwrap_or(J::Null),
138            WhiskerValue::String(s) => J::String(s.clone()),
139            // Events never carry bytes; module values might. Render
140            // as an array of byte-valued numbers so the mapping is
141            // total and lossless.
142            WhiskerValue::Bytes(b) => {
143                J::Array(b.iter().map(|x| J::Number((*x as u64).into())).collect())
144            }
145            WhiskerValue::Array(a) => J::Array(a.iter().map(WhiskerValue::to_json).collect()),
146            WhiskerValue::Map(m) => {
147                J::Object(m.iter().map(|(k, v)| (k.clone(), v.to_json())).collect())
148            }
149            // An Error reaching a typed-deserialize target is a
150            // dispatch failure; surface its message as a string so a
151            // `String`-typed field can still read it.
152            WhiskerValue::Error(e) => J::String(e.clone()),
153        }
154    }
155
156    /// Lift a `serde_json::Value` into a `WhiskerValue`.
157    pub fn from_json(v: serde_json::Value) -> WhiskerValue {
158        use serde_json::Value as J;
159        match v {
160            J::Null => WhiskerValue::Null,
161            J::Bool(b) => WhiskerValue::Bool(b),
162            J::Number(n) => {
163                if let Some(i) = n.as_i64() {
164                    WhiskerValue::Int(i)
165                } else if let Some(f) = n.as_f64() {
166                    WhiskerValue::Float(f)
167                } else {
168                    WhiskerValue::Null
169                }
170            }
171            J::String(s) => WhiskerValue::String(s),
172            J::Array(a) => {
173                WhiskerValue::Array(a.into_iter().map(WhiskerValue::from_json).collect())
174            }
175            J::Object(o) => WhiskerValue::Map(
176                o.into_iter()
177                    .map(|(k, v)| (k, WhiskerValue::from_json(v)))
178                    .collect(),
179            ),
180        }
181    }
182}
183
184// Deserialize impl: lets a typed event struct hold an arbitrary
185// sub-tree (a `data-*` dataset, a custom event's `detail`) as a
186// `WhiskerValue` field without leaking `serde_json::Value` into the
187// public event API. Mirrors `serde_json::Value`'s own visitor — each
188// serde scalar / seq / map maps to the matching variant.
189impl<'de> Deserialize<'de> for WhiskerValue {
190    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
191    where
192        D: Deserializer<'de>,
193    {
194        struct WhiskerValueVisitor;
195
196        impl<'de> Visitor<'de> for WhiskerValueVisitor {
197            type Value = WhiskerValue;
198
199            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200                f.write_str("any JSON-compatible value")
201            }
202
203            fn visit_unit<E>(self) -> Result<WhiskerValue, E> {
204                Ok(WhiskerValue::Null)
205            }
206            fn visit_none<E>(self) -> Result<WhiskerValue, E> {
207                Ok(WhiskerValue::Null)
208            }
209            fn visit_some<D>(self, d: D) -> Result<WhiskerValue, D::Error>
210            where
211                D: Deserializer<'de>,
212            {
213                Deserialize::deserialize(d)
214            }
215            fn visit_bool<E>(self, v: bool) -> Result<WhiskerValue, E> {
216                Ok(WhiskerValue::Bool(v))
217            }
218            fn visit_i64<E>(self, v: i64) -> Result<WhiskerValue, E> {
219                Ok(WhiskerValue::Int(v))
220            }
221            fn visit_u64<E>(self, v: u64) -> Result<WhiskerValue, E> {
222                Ok(i64::try_from(v).map_or(WhiskerValue::Float(v as f64), WhiskerValue::Int))
223            }
224            fn visit_f64<E>(self, v: f64) -> Result<WhiskerValue, E> {
225                Ok(WhiskerValue::Float(v))
226            }
227            fn visit_str<E>(self, v: &str) -> Result<WhiskerValue, E> {
228                Ok(WhiskerValue::String(v.to_owned()))
229            }
230            fn visit_string<E>(self, v: String) -> Result<WhiskerValue, E> {
231                Ok(WhiskerValue::String(v))
232            }
233            fn visit_bytes<E>(self, v: &[u8]) -> Result<WhiskerValue, E> {
234                Ok(WhiskerValue::Bytes(v.to_owned()))
235            }
236            fn visit_seq<A>(self, mut seq: A) -> Result<WhiskerValue, A::Error>
237            where
238                A: SeqAccess<'de>,
239            {
240                let mut out = Vec::new();
241                while let Some(item) = seq.next_element()? {
242                    out.push(item);
243                }
244                Ok(WhiskerValue::Array(out))
245            }
246            fn visit_map<A>(self, mut map: A) -> Result<WhiskerValue, A::Error>
247            where
248                A: MapAccess<'de>,
249            {
250                let mut out = BTreeMap::new();
251                while let Some((k, v)) = map.next_entry::<String, WhiskerValue>()? {
252                    out.insert(k, v);
253                }
254                Ok(WhiskerValue::Map(out))
255            }
256        }
257
258        deserializer.deserialize_any(WhiskerValueVisitor)
259    }
260}
261
262/// Failure surface for the `#[whisker::module_component]`-generated
263/// proxy methods and `ElementRef::invoke_typed`.
264///
265/// Wraps the UTF-8 description the bridge returned via
266/// [`WhiskerValue::Error`] (unknown module / missing method /
267/// platform-side exception), plus type-mismatch messages a proxy
268/// synthesises when the bridge returned an unexpected variant for
269/// the declared return type. Implements [`std::error::Error`] so
270/// callers can `?`-propagate through `Result` chains.
271#[derive(Debug, Clone, PartialEq, Eq)]
272pub struct WhiskerModuleError(pub String);
273
274impl std::fmt::Display for WhiskerModuleError {
275    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276        f.write_str(&self.0)
277    }
278}
279
280impl std::error::Error for WhiskerModuleError {}
281
282impl From<()> for WhiskerValue {
283    fn from(_: ()) -> Self {
284        WhiskerValue::Null
285    }
286}
287impl From<bool> for WhiskerValue {
288    fn from(v: bool) -> Self {
289        WhiskerValue::Bool(v)
290    }
291}
292impl From<i32> for WhiskerValue {
293    fn from(v: i32) -> Self {
294        WhiskerValue::Int(v as i64)
295    }
296}
297impl From<i64> for WhiskerValue {
298    fn from(v: i64) -> Self {
299        WhiskerValue::Int(v)
300    }
301}
302impl From<u32> for WhiskerValue {
303    fn from(v: u32) -> Self {
304        WhiskerValue::Int(v as i64)
305    }
306}
307impl From<f32> for WhiskerValue {
308    fn from(v: f32) -> Self {
309        WhiskerValue::Float(v as f64)
310    }
311}
312impl From<f64> for WhiskerValue {
313    fn from(v: f64) -> Self {
314        WhiskerValue::Float(v)
315    }
316}
317impl From<String> for WhiskerValue {
318    fn from(v: String) -> Self {
319        WhiskerValue::String(v)
320    }
321}
322impl From<&str> for WhiskerValue {
323    fn from(v: &str) -> Self {
324        WhiskerValue::String(v.to_string())
325    }
326}
327impl From<Vec<u8>> for WhiskerValue {
328    fn from(v: Vec<u8>) -> Self {
329        WhiskerValue::Bytes(v)
330    }
331}
332impl<T> From<Vec<T>> for WhiskerValue
333where
334    T: Into<WhiskerValue>,
335{
336    fn from(v: Vec<T>) -> Self {
337        WhiskerValue::Array(v.into_iter().map(Into::into).collect())
338    }
339}
340
341// TryFrom impls — used by `ElementRef::invoke_typed<T>` so authors
342// can write `r.invoke_typed::<f64>("currentTime", vec![])`. The
343// `Error` payload is a `String` so it folds cleanly into
344// `RefError::DispatchFailed.message` without an extra map step.
345
346impl TryFrom<WhiskerValue> for () {
347    type Error = String;
348    fn try_from(v: WhiskerValue) -> Result<Self, Self::Error> {
349        match v {
350            WhiskerValue::Null => Ok(()),
351            other => Err(format!("expected Null, got {other:?}")),
352        }
353    }
354}
355
356impl TryFrom<WhiskerValue> for bool {
357    type Error = String;
358    fn try_from(v: WhiskerValue) -> Result<Self, Self::Error> {
359        match v {
360            WhiskerValue::Bool(b) => Ok(b),
361            other => Err(format!("expected Bool, got {other:?}")),
362        }
363    }
364}
365
366impl TryFrom<WhiskerValue> for i64 {
367    type Error = String;
368    fn try_from(v: WhiskerValue) -> Result<Self, Self::Error> {
369        match v {
370            WhiskerValue::Int(i) => Ok(i),
371            other => Err(format!("expected Int, got {other:?}")),
372        }
373    }
374}
375
376impl TryFrom<WhiskerValue> for i32 {
377    type Error = String;
378    fn try_from(v: WhiskerValue) -> Result<Self, Self::Error> {
379        match v {
380            WhiskerValue::Int(i) => {
381                i32::try_from(i).map_err(|_| format!("Int {i} out of range for i32"))
382            }
383            other => Err(format!("expected Int, got {other:?}")),
384        }
385    }
386}
387
388impl TryFrom<WhiskerValue> for f64 {
389    type Error = String;
390    fn try_from(v: WhiskerValue) -> Result<Self, Self::Error> {
391        match v {
392            WhiskerValue::Float(f) => Ok(f),
393            // Widen Int → Float so platforms that return integer-valued
394            // numbers don't trip up callers asking for f64.
395            WhiskerValue::Int(i) => Ok(i as f64),
396            other => Err(format!("expected Float, got {other:?}")),
397        }
398    }
399}
400
401impl TryFrom<WhiskerValue> for f32 {
402    type Error = String;
403    fn try_from(v: WhiskerValue) -> Result<Self, Self::Error> {
404        f64::try_from(v).map(|f| f as f32)
405    }
406}
407
408impl TryFrom<WhiskerValue> for String {
409    type Error = String;
410    fn try_from(v: WhiskerValue) -> Result<Self, Self::Error> {
411        match v {
412            WhiskerValue::String(s) => Ok(s),
413            other => Err(format!("expected String, got {other:?}")),
414        }
415    }
416}
417
418impl TryFrom<WhiskerValue> for Vec<u8> {
419    type Error = String;
420    fn try_from(v: WhiskerValue) -> Result<Self, Self::Error> {
421        match v {
422            WhiskerValue::Bytes(b) => Ok(b),
423            other => Err(format!("expected Bytes, got {other:?}")),
424        }
425    }
426}