pub mod json;
#[cfg(feature = "lua")]
pub mod lua;
pub mod message;
pub use message::{MessageError, MessageField};
#[cfg(feature = "derive")]
pub use kui_derive::Message;
#[derive(Debug, Default, PartialEq)]
pub enum Value {
#[default]
Null,
Bool(bool),
Int(i64),
Float(f64),
Str(String),
List(Vec<Value>),
Map(Vec<(String, Value)>),
}
impl Clone for Value {
fn clone(&self) -> Self {
match self {
Value::Null => Value::Null,
Value::Bool(b) => Value::Bool(*b),
Value::Int(i) => Value::Int(*i),
Value::Float(f) => Value::Float(*f),
Value::Str(s) => Value::Str(s.clone()),
Value::List(items) => Value::List(items.clone()),
Value::Map(entries) => Value::Map(entries.clone()),
}
}
fn clone_from(&mut self, source: &Self) {
match (self, source) {
(Value::Str(a), Value::Str(b)) => a.clone_from(b),
(Value::List(a), Value::List(b)) => a.clone_from(b),
(Value::Map(a), Value::Map(b)) => {
a.truncate(b.len());
for ((ka, va), (kb, vb)) in a.iter_mut().zip(b) {
ka.clone_from(kb);
va.clone_from(vb);
}
let have = a.len();
a.extend(b[have..].iter().cloned());
}
(this, source) => *this = source.clone(),
}
}
}
impl Value {
#[inline]
pub fn owns_buffers(&self) -> bool {
matches!(self, Value::Str(_) | Value::List(_) | Value::Map(_))
}
pub fn str(s: impl Into<String>) -> Value {
Value::Str(s.into())
}
pub fn map(entries: impl IntoIterator<Item = (&'static str, Value)>) -> Value {
Value::Map(
entries
.into_iter()
.map(|(k, v)| (k.to_string(), v))
.collect(),
)
}
pub fn get(&self, key: &str) -> Option<&Value> {
match self {
Value::Map(entries) => entries.iter().find(|(k, _)| k == key).map(|(_, v)| v),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::Str(s) => Some(s),
_ => None,
}
}
pub fn as_int(&self) -> Option<i64> {
match self {
Value::Int(i) => Some(*i),
Value::Float(f) => Some(*f as i64),
_ => None,
}
}
pub fn as_list(&self) -> Option<&[Value]> {
match self {
Value::List(items) => Some(items),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
Value::Float(f) => Some(*f),
Value::Int(i) => Some(*i as f64),
_ => None,
}
}
pub fn type_name(&self) -> &'static str {
match self {
Value::Null => "nil",
Value::Bool(_) => "boolean",
Value::Int(_) | Value::Float(_) => "number",
Value::Str(_) => "string",
Value::List(_) => "list",
Value::Map(_) => "map",
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Bool(b) => Some(*b),
_ => None,
}
}
#[inline]
pub fn get_str(&self, key: &str) -> Option<&str> {
self.get(key).and_then(Value::as_str)
}
#[inline]
pub fn get_int(&self, key: &str) -> Option<i64> {
self.get(key).and_then(Value::as_int)
}
#[inline]
pub fn get_float(&self, key: &str) -> Option<f64> {
self.get(key).and_then(Value::as_float)
}
#[inline]
pub fn get_f32(&self, key: &str) -> Option<f32> {
self.get_float(key).map(|v| v as f32)
}
#[inline]
pub fn get_bool(&self, key: &str) -> Option<bool> {
self.get(key).and_then(Value::as_bool)
}
}
impl Value {
pub fn float(v: f32) -> Value {
Value::Float(v as f64)
}
pub fn opt<T>(v: Option<T>, f: impl FnOnce(T) -> Value) -> Value {
v.map_or(Value::Null, f)
}
pub fn opt_str(s: &Option<String>) -> Value {
Value::opt(s.as_ref(), |s| Value::Str(s.clone()))
}
pub fn opt_float(v: Option<f32>) -> Value {
Value::opt(v, Value::float)
}
pub fn opt_usize(v: Option<usize>) -> Value {
Value::opt(v, |v| Value::Int(v as i64))
}
pub fn opt_bool(v: Option<bool>) -> Value {
Value::opt(v, Value::Bool)
}
pub fn list(items: impl IntoIterator<Item = Value>) -> Value {
Value::List(items.into_iter().collect())
}
pub fn floats(items: &[f32]) -> Value {
Value::list(items.iter().map(|v| Value::float(*v)))
}
}
impl TryFrom<&Value> for String {
type Error = ();
fn try_from(v: &Value) -> Result<Self, ()> {
v.as_str().map(str::to_string).ok_or(())
}
}
impl From<&Value> for Value {
fn from(v: &Value) -> Self {
v.clone()
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::Str(s.to_string())
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::Str(s)
}
}
impl From<i64> for Value {
fn from(v: i64) -> Self {
Value::Int(v)
}
}
impl From<i32> for Value {
fn from(v: i32) -> Self {
Value::Int(v.into())
}
}
impl From<u32> for Value {
fn from(v: u32) -> Self {
Value::Int(v.into())
}
}
impl From<usize> for Value {
fn from(v: usize) -> Self {
Value::Int(i64::try_from(v).unwrap_or(i64::MAX))
}
}
impl From<f32> for Value {
fn from(v: f32) -> Self {
Value::float(v)
}
}
impl From<f64> for Value {
fn from(v: f64) -> Self {
Value::Float(v)
}
}
impl From<bool> for Value {
fn from(v: bool) -> Self {
Value::Bool(v)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn map_get_finds_entries() {
let v = Value::map([("kind", Value::str("inc")), ("by", Value::Int(2))]);
assert_eq!(v.get("kind").and_then(Value::as_str), Some("inc"));
assert_eq!(v.get("by").and_then(Value::as_int), Some(2));
assert_eq!(v.get("missing"), None);
}
#[test]
fn clone_from_is_a_clone_whatever_it_overwrites() {
let shapes = [
Value::Null,
Value::Bool(true),
Value::Int(-3),
Value::Float(2.5),
Value::str("row"),
Value::str(""),
Value::List(vec![Value::Int(1), Value::str("two")]),
Value::List(Vec::new()),
Value::map([("kind", Value::str("row")), ("line", Value::Int(7))]),
Value::map([
("kind", Value::str("a longer kind")),
("x", Value::Float(1.0)),
]),
Value::map([("only", Value::Null)]),
Value::map([
("kind", Value::str("row")),
("line", Value::Int(7)),
(
"nested",
Value::map([("deep", Value::List(vec![Value::Bool(false)]))]),
),
]),
];
for into in &shapes {
for from in &shapes {
let mut v = into.clone();
v.clone_from(from);
assert_eq!(&v, from, "{from:?} written over {into:?}");
}
}
}
#[test]
fn clone_from_keeps_the_buffers_of_a_value_like_it() {
let mut v = Value::map([("kind", Value::str("row")), ("line", Value::Int(1))]);
let Value::Map(entries) = &v else {
unreachable!()
};
let (keys, text) = (entries.as_ptr(), entries[0].0.as_ptr());
v.clone_from(&Value::map([
("kind", Value::str("cut")),
("line", Value::Int(2)),
]));
let Value::Map(entries) = &v else {
unreachable!()
};
assert_eq!((entries.as_ptr(), entries[0].0.as_ptr()), (keys, text));
assert_eq!(v.get_str("kind"), Some("cut"));
}
#[test]
fn numeric_coercions() {
assert_eq!(Value::Int(3).as_float(), Some(3.0));
assert_eq!(Value::Float(3.7).as_int(), Some(3));
assert_eq!(Value::str("x").as_int(), None);
}
}