1pub mod json;
33#[cfg(feature = "lua")]
34pub mod lua;
35pub mod message;
36
37pub use message::{MessageError, MessageField};
38
39#[cfg(feature = "derive")]
43pub use kui_derive::Message;
44
45#[derive(Clone, 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 Value {
60 pub fn str(s: impl Into<String>) -> Value {
62 Value::Str(s.into())
63 }
64
65 pub fn map(entries: impl IntoIterator<Item = (&'static str, Value)>) -> Value {
67 Value::Map(
68 entries
69 .into_iter()
70 .map(|(k, v)| (k.to_string(), v))
71 .collect(),
72 )
73 }
74
75 pub fn get(&self, key: &str) -> Option<&Value> {
78 match self {
79 Value::Map(entries) => entries.iter().find(|(k, _)| k == key).map(|(_, v)| v),
80 _ => None,
81 }
82 }
83
84 pub fn as_str(&self) -> Option<&str> {
85 match self {
86 Value::Str(s) => Some(s),
87 _ => None,
88 }
89 }
90
91 pub fn as_int(&self) -> Option<i64> {
93 match self {
94 Value::Int(i) => Some(*i),
95 Value::Float(f) => Some(*f as i64),
96 _ => None,
97 }
98 }
99
100 pub fn as_list(&self) -> Option<&[Value]> {
101 match self {
102 Value::List(items) => Some(items),
103 _ => None,
104 }
105 }
106
107 pub fn as_float(&self) -> Option<f64> {
109 match self {
110 Value::Float(f) => Some(*f),
111 Value::Int(i) => Some(*i as f64),
112 _ => None,
113 }
114 }
115
116 pub fn type_name(&self) -> &'static str {
118 match self {
119 Value::Null => "nil",
120 Value::Bool(_) => "boolean",
121 Value::Int(_) | Value::Float(_) => "number",
122 Value::Str(_) => "string",
123 Value::List(_) => "list",
124 Value::Map(_) => "map",
125 }
126 }
127
128 pub fn as_bool(&self) -> Option<bool> {
129 match self {
130 Value::Bool(b) => Some(*b),
131 _ => None,
132 }
133 }
134
135 #[inline]
137 pub fn get_str(&self, key: &str) -> Option<&str> {
138 self.get(key).and_then(Value::as_str)
139 }
140
141 #[inline]
143 pub fn get_int(&self, key: &str) -> Option<i64> {
144 self.get(key).and_then(Value::as_int)
145 }
146
147 #[inline]
149 pub fn get_float(&self, key: &str) -> Option<f64> {
150 self.get(key).and_then(Value::as_float)
151 }
152
153 #[inline]
156 pub fn get_f32(&self, key: &str) -> Option<f32> {
157 self.get_float(key).map(|v| v as f32)
158 }
159
160 #[inline]
162 pub fn get_bool(&self, key: &str) -> Option<bool> {
163 self.get(key).and_then(Value::as_bool)
164 }
165}
166
167impl Value {
168 pub fn float(v: f32) -> Value {
170 Value::Float(v as f64)
171 }
172
173 pub fn opt<T>(v: Option<T>, f: impl FnOnce(T) -> Value) -> Value {
176 v.map_or(Value::Null, f)
177 }
178
179 pub fn opt_str(s: &Option<String>) -> Value {
180 Value::opt(s.as_ref(), |s| Value::Str(s.clone()))
181 }
182
183 pub fn opt_float(v: Option<f32>) -> Value {
184 Value::opt(v, Value::float)
185 }
186
187 pub fn opt_usize(v: Option<usize>) -> Value {
188 Value::opt(v, |v| Value::Int(v as i64))
189 }
190
191 pub fn opt_bool(v: Option<bool>) -> Value {
192 Value::opt(v, Value::Bool)
193 }
194
195 pub fn list(items: impl IntoIterator<Item = Value>) -> Value {
196 Value::List(items.into_iter().collect())
197 }
198
199 pub fn floats(items: &[f32]) -> Value {
200 Value::list(items.iter().map(|v| Value::float(*v)))
201 }
202}
203
204impl TryFrom<&Value> for String {
208 type Error = ();
209
210 fn try_from(v: &Value) -> Result<Self, ()> {
211 v.as_str().map(str::to_string).ok_or(())
212 }
213}
214
215impl From<&Value> for Value {
218 fn from(v: &Value) -> Self {
219 v.clone()
220 }
221}
222
223impl From<&str> for Value {
224 fn from(s: &str) -> Self {
225 Value::Str(s.to_string())
226 }
227}
228
229impl From<String> for Value {
230 fn from(s: String) -> Self {
231 Value::Str(s)
232 }
233}
234
235impl From<i64> for Value {
236 fn from(v: i64) -> Self {
237 Value::Int(v)
238 }
239}
240
241impl From<i32> for Value {
242 fn from(v: i32) -> Self {
243 Value::Int(v.into())
244 }
245}
246
247impl From<u32> for Value {
248 fn from(v: u32) -> Self {
249 Value::Int(v.into())
250 }
251}
252
253impl From<usize> for Value {
256 fn from(v: usize) -> Self {
257 Value::Int(i64::try_from(v).unwrap_or(i64::MAX))
258 }
259}
260
261impl From<f32> for Value {
262 fn from(v: f32) -> Self {
263 Value::float(v)
264 }
265}
266
267impl From<f64> for Value {
268 fn from(v: f64) -> Self {
269 Value::Float(v)
270 }
271}
272
273impl From<bool> for Value {
274 fn from(v: bool) -> Self {
275 Value::Bool(v)
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn map_get_finds_entries() {
285 let v = Value::map([("kind", Value::str("inc")), ("by", Value::Int(2))]);
286 assert_eq!(v.get("kind").and_then(Value::as_str), Some("inc"));
287 assert_eq!(v.get("by").and_then(Value::as_int), Some(2));
288 assert_eq!(v.get("missing"), None);
289 }
290
291 #[test]
292 fn numeric_coercions() {
293 assert_eq!(Value::Int(3).as_float(), Some(3.0));
294 assert_eq!(Value::Float(3.7).as_int(), Some(3));
295 assert_eq!(Value::str("x").as_int(), None);
296 }
297}