Skip to main content

kui_core/
message.rs

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