Skip to main content

kui_core/
message.rs

1//! Typed messages over the plain-data payload (backlog C50).
2//!
3//! The IR's payloads are [`Value`]s on purpose: Lua, C and JSX share them.
4//! A Rust app gets the exhaustive `match` back on its own side of that
5//! contract: `#[derive(Message)]` (the `kui-derive` crate, re-exported by
6//! `kui-native` and, behind its `derive` feature, by this crate) turns an enum into
7//! a `{kind, …fields}` map and back, so `on_click(Msg::Save)` builds the
8//! payload and `ev.message::<Msg>()` reads it.
9//!
10//! What the derive needs from this module: [`MessageField`], the
11//! conversion each field's type has, and [`MessageError`], what a payload
12//! that is not one of the enum's says. Both are plain enough to write by
13//! hand for a type the derive does not cover.
14
15use crate::value::Value;
16
17/// A type a message field can hold: to a [`Value`] and back. Implemented
18/// for the numbers, `bool`, `String`, `Value` itself, `Option` (absent or
19/// null is `None`) and `Vec`; `#[derive(Message)]` implements it for the
20/// type it derives, so one message can carry another.
21pub trait MessageField: Sized {
22    fn to_value(self) -> Value;
23    /// `None` for a value of the wrong shape.
24    fn from_value(v: &Value) -> Option<Self>;
25}
26
27/// Why a payload is not the message it was read as.
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub enum MessageError {
30    /// It has no `kind` string (and, for a string-shaped message, is not a
31    /// string).
32    NoKind,
33    /// Its `kind` names no variant of the type.
34    UnknownKind(String),
35    /// A field is missing, or holds a value of the wrong shape.
36    Field { kind: String, field: &'static str },
37}
38
39impl std::fmt::Display for MessageError {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            MessageError::NoKind => f.write_str("the payload has no `kind`"),
43            MessageError::UnknownKind(k) => write!(f, "no message is of kind {k:?}"),
44            MessageError::Field { kind, field } => write!(
45                f,
46                "a {kind:?} message's `{field}` is missing or of the wrong type"
47            ),
48        }
49    }
50}
51
52impl std::error::Error for MessageError {}
53
54/// Field `key` of the map `v`, as `T`: what the derive reads each field
55/// with. An absent key reads as null, so an `Option` field may be left out.
56pub fn field<T: MessageField>(v: &Value, kind: &str, key: &'static str) -> Result<T, MessageError> {
57    T::from_value(v.get(key).unwrap_or(&Value::Null)).ok_or_else(|| MessageError::Field {
58        kind: kind.to_string(),
59        field: key,
60    })
61}
62
63/// The `kind` of a map payload, for the derive's match.
64pub fn kind_of(v: &Value) -> Result<&str, MessageError> {
65    v.get_str("kind").ok_or(MessageError::NoKind)
66}
67
68impl MessageField for Value {
69    fn to_value(self) -> Value {
70        self
71    }
72    fn from_value(v: &Value) -> Option<Self> {
73        Some(v.clone())
74    }
75}
76
77impl MessageField for bool {
78    fn to_value(self) -> Value {
79        Value::Bool(self)
80    }
81    fn from_value(v: &Value) -> Option<Self> {
82        v.as_bool()
83    }
84}
85
86impl MessageField for String {
87    fn to_value(self) -> Value {
88        Value::Str(self)
89    }
90    fn from_value(v: &Value) -> Option<Self> {
91        v.as_str().map(str::to_string)
92    }
93}
94
95impl MessageField for f64 {
96    fn to_value(self) -> Value {
97        Value::Float(self)
98    }
99    fn from_value(v: &Value) -> Option<Self> {
100        v.as_float()
101    }
102}
103
104impl MessageField for f32 {
105    fn to_value(self) -> Value {
106        Value::float(self)
107    }
108    fn from_value(v: &Value) -> Option<Self> {
109        v.as_float().map(|f| f as f32)
110    }
111}
112
113/// The integers, through `Value::Int`, refusing one out of the type's range
114/// rather than wrapping it.
115macro_rules! int_fields {
116    ($($t:ty),*) => {$(
117        impl MessageField for $t {
118            fn to_value(self) -> Value {
119                Value::Int(i64::try_from(self).unwrap_or(i64::MAX))
120            }
121            fn from_value(v: &Value) -> Option<Self> {
122                v.as_int().and_then(|n| <$t>::try_from(n).ok())
123            }
124        }
125    )*};
126}
127int_fields!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
128
129impl<T: MessageField> MessageField for Option<T> {
130    fn to_value(self) -> Value {
131        self.map_or(Value::Null, T::to_value)
132    }
133    fn from_value(v: &Value) -> Option<Self> {
134        match v {
135            Value::Null => Some(None),
136            v => T::from_value(v).map(Some),
137        }
138    }
139}
140
141impl<T: MessageField> MessageField for Vec<T> {
142    fn to_value(self) -> Value {
143        Value::List(self.into_iter().map(T::to_value).collect())
144    }
145    fn from_value(v: &Value) -> Option<Self> {
146        v.as_list()?.iter().map(T::from_value).collect()
147    }
148}
149
150impl<T: MessageField> MessageField for Box<T> {
151    fn to_value(self) -> Value {
152        (*self).to_value()
153    }
154    fn from_value(v: &Value) -> Option<Self> {
155        T::from_value(v).map(Box::new)
156    }
157}
158
159impl crate::input::UiEvent {
160    /// The app's message this event carries, as `M`: the payload itself for
161    /// a click (what `on_click` was handed), and otherwise the `tag` inside
162    /// a core event — a drag, a change, a scroll, a drop, a layout — which
163    /// is what `on_drag` and the rest were handed. `None` when neither is
164    /// an `M`: a key press, a core event with no tag, another type's.
165    ///
166    /// The event's own fields stay on `payload` — a drag's `phase` and
167    /// `x`/`y`, a change's `value` — so a handler matches on the message and
168    /// reads those beside it.
169    pub fn message<M>(&self) -> Option<M>
170    where
171        M: for<'a> TryFrom<&'a Value>,
172    {
173        M::try_from(&self.payload)
174            .ok()
175            .or_else(|| self.payload.get("tag").and_then(|t| M::try_from(t).ok()))
176    }
177}