use crate::{config, get_global_mize}; use crate::config::{ConfigOpt, FromItemData, MizeFun}; use ciborium::into_writer; use ciborium::from_reader; use colored::Colorize; use serde::de::DeserializeOwned; use core::fmt; use serde::Deserialize; use serde::Serialize; use std::any::{Any, TypeId}; use std::collections::HashMap; use std::fmt::Debug; use std::sync::MutexGuard; use std::{boxed, io}; use std::fmt::Display; use std::i128; use crate::tracing::instrument; use crate::prelude::*; use crate::error::{IntoMizeResult, MizeError, MizeErrorWhichImplementsError, MizeResult}; use crate::id::MizeId; use crate::instance::connection::value_raw_con_by_id; use crate::instance::Mize; use crate::mize_err; use crate::proto::MizeMessage; use ciborium::Value as CborValue; // a item always has to do with a Instance, which takes care of how it is updated #[derive(Debug, Clone)] pub struct Item<'a> { id: MizeId, pub instance: &'a Mize, } // without an Instance it is not an item, but only the "data of an item" // and this type for now is just an alias to CborValue #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ItemData(pub CborValue); impl Item<'_> { pub fn id(&self) -> MizeId { self.id.clone() } pub fn new(id: MizeId, instance: &Mize) -> Item { Item { id, instance } } pub fn value_raw(&self) -> MizeResult> { let data = self.as_data_full()?; let raw = get_raw_from_cbor(data.cbor(), vec![])?; return Ok(raw.to_owned()); } pub fn value_string(&self) -> MizeResult { let raw = self.value_raw()?; String::from_utf8(raw.clone()) .mres_msg(format!("failed to convert raw value from item '{}' into a utf-8 string. raw value was: '{:02X?}'", self.id(), raw)) } pub fn as_data_full(&self) -> MizeResult { let id = self.id(); // the instance we are if id.store_part() == "self" { debug!("getting item '{}' from self", self.id()); match id.nth_part(1)? { "con_by_id" => { return value_raw_con_by_id(&mut self.clone()); } "namespace" => { let namespace_inner = self.instance.namespace.lock()?; return Ok(ItemData::from_string(namespace_inner.as_real_string())); } "self_namespace" => { let namespace_inner = self.instance.self_namespace.lock()?; return Ok(ItemData::from_string(namespace_inner.as_real_string())); } "config" => { let rest_path = as Clone>::clone(&id .path) .into_iter() .skip(2) .map(|v| v.to_owned()) .collect::>() .join("."); let mut mize = self.instance.to_owned(); return Ok(mize.get_config(rest_path.as_str())?); } _ => {} } return Err(mize_err!( "a /self path, but the next element in the path '{:?}' is not valid", id.nth_part(1) )); } let self_namespace_inner = self.instance.self_namespace.lock()?; if self.id().namespace() == self_namespace_inner.to_owned() { debug!("getting item '{}' from store", self.id()); if id.store_part() == "inst" { match id.nth_part(1)? { "con_by_id" => { return value_raw_con_by_id(&mut self.clone()); } "namespace" => { let namespace_inner = self.instance.namespace.lock()?; return Ok(ItemData::from_string(namespace_inner.as_real_string())); } "self_namespace" => { let namespace_inner = self.instance.self_namespace.lock()?; return Ok(ItemData::from_string(namespace_inner.as_real_string())); } _ => {} } } let store_inner = self.instance.store.lock()?; return store_inner.get_value_data_full(self.id()); } else { let connection = self.instance.get_connection_by_ns(self.id().namespace())?; debug!( "getting item '{}' from connection '{}' from store", self.id(), connection.id ); let msg = MizeMessage::new_get(self.id(), connection.id); connection.send(msg); let data = self.instance.give_msg_wait(self.id())?; return Ok(data); } } #[instrument(name = "fn.ItemData::merge")] pub fn merge + Debug>(&mut self, value: V) -> MizeResult<()> { let mut data = self.as_data_full()?; //let mut data = data_full.get_path(id_path_without_store_part)?; trace!("item::merge data: {:?}", data); trace!("item::merge id: {:?}", self.id()); let new_data = value.into(); data.merge(new_data); trace!("item::merge new_data: {:?}", data); // handle the case of /self/* if self.id().store_part() == "self" { let rest_path = as Clone>::clone(&self .id() .path) .into_iter() .skip(1) .map(|v| v.to_owned()) .collect::>() .join("/"); let id_for_store = self .instance .new_id("0".to_owned() + "/" + rest_path.as_str())?; let store_inner = self.instance.store.lock()?; store_inner.set(id_for_store, data)?; } else if self.instance.we_are_namespace()? { let store_inner = self.instance.store.lock()?; store_inner.set(self.id(), data)?; } else { let namespace = self.id().namespace(); let connection = self.instance.get_connection_by_ns(namespace)?; let msg = MizeMessage::new_update_request(self.id(), data, connection.id); connection.send(msg)?; } Ok(()) } } impl<'a> Display for Item<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { //let item_data = self.as_data_full().map_err(|_| std::fmt::Error)?; write!(f, "Item with id: {}", self.id()); return Ok(()); } } impl ItemData { pub fn new() -> ItemData { ItemData(CborValue::Null) } pub fn empty() -> ItemData { ItemData(CborValue::Null) } pub fn from_toml(toml_string: &str) -> MizeResult { let toml_deserializer = toml::Deserializer::new(&toml_string); let value = CborValue::deserialize(toml_deserializer).mres_msg(format!( "Could not deserialize the toml string: {}", &toml_string ))?; return Ok(value.into_item_data()); } pub fn to_toml(&self) -> MizeResult { let mut out_string = String::new(); let toml_serializer = toml::Serializer::new(&mut out_string); CborValue::serialize(&self.cbor(), toml_serializer)?; Ok(out_string) } pub fn from_serializable(val: T) -> MizeResult { let mut buffer = Vec::new(); into_writer(&val, &mut buffer)?; let cbor_val: CborValue = from_reader(buffer.as_slice())?; Ok(ItemData(cbor_val)) } /// Convert any native Rust value to ItemData... /// Stores the value in the global Mize registry and returns an ItemData with _native: pub fn from_native(val: T) -> MizeResult { let mize = get_global_mize()?; let mut native_values = mize.native_values.lock().map_err(|_| mize_err!("Failed to lock native_values"))?; let mut next_id = mize.next_native_value_id.lock().map_err(|_| mize_err!("Failed to lock next_native_value_id"))?; let id = *next_id; *next_id += 1; native_values.insert(id, Box::new(val)); let mut data = ItemData::new(); data.set_path("_native", id)?; Ok(data) } pub fn value_string(self) -> MizeResult { match self.cbor() { CborValue::Text(string) => Ok(string.to_owned()), CborValue::Bool(val) => Ok(format!("{val}")), CborValue::Integer(val) => Ok(format!("{}", u32::try_from(val.clone())?)), CborValue::Float(val) => Ok(format!("{val}")), _ => Err(mize_err!("this ItemData has no string value")), } } //pub fn value_fun(&self) -> MizeResult { pub fn value_fun(&self) -> MizeResult<()> { let mut mize = get_global_mize()?; let mut fun_values = mize.fun_values.lock()?; let fun_id = self.get_path("_func")?.value_u32()?; //let fun = fun_values.remove(fun_id).ok_or(mize_err!("fun with id {fun_id} not in fun_values"))?; // TODO: finish Ok(()) } pub fn call(&self, args: ItemData) -> MizeResult { let mut mize = get_global_mize()?; let mut fun_values = mize.fun_values.lock()?; let fun_num = self.get_path("_fun_num")?.value_u32()?; let fun_id = mize.clone().id_new_dots("mize.fun")?.join(format!("{fun_num}").as_str())?; let fun = fun_values.remove(&fun_id).ok_or(mize_err!("fun with id {fun_id} not in fun_values"))?; drop(fun_values); let res = fun(args)?; let mut fun_values = mize.fun_values.lock()?; fun_values.insert(fun_id, fun); return Ok(res); } pub fn fun(fun: MizeFun) -> MizeResult { let mut mize = get_global_mize()?; let mut fun_values = mize.fun_values.lock()?; let mut next_fun_values_id = mize.next_fun_values_id.lock()?; let fun_num = next_fun_values_id.clone(); *next_fun_values_id = *next_fun_values_id +1; let fun_id = mize.clone().id_new_dots("mize.fun")?.join(format!("{fun_num}").as_str())?; fun_values.insert(fun_id, fun); let mut ret = ItemData::new(); ret.set_path("_fun_num", fun_num)?; Ok(ret) } pub fn value_u32(&self) -> MizeResult { Ok(u32::try_from(self.cbor().as_integer().ok_or("value_u32 but data wos not a cbor Integer")?)?) } pub fn value_bool(self) -> MizeResult { match self.cbor() { CborValue::Bool(val) => Ok(*val), _ => Err(mize_err!("this ItemData has no string value")), } } pub fn as_bool(self) -> Option { match self.cbor() { CborValue::Bool(val) => Some(*val), _ => None, } } pub fn from_string>(into_string: S) -> ItemData { let string = into_string.into(); let data = CborValue::Text(string); return ItemData(data); } pub fn to_json(self) -> MizeResult { let mut result: Vec = Vec::new(); self.0 .serialize(&mut serde_json::Serializer::new(&mut result))?; Ok(String::from_utf8(result)?) } pub fn from_json(json_str: String) -> MizeResult { let mut json_deserializer = serde_json::Deserializer::from_str(json_str.as_str()); let value = CborValue::deserialize(&mut json_deserializer).mres_msg(format!( "could not deserialize the json string: {}", json_str ))?; return Ok(value.into_item_data()); } pub fn merge(&mut self, other: ItemData) { item_data_merge(&mut self.0, &other.0); } pub fn cbor(&self) -> &CborValue { &self.0 } pub fn from_cbor(cbor: CborValue) -> ItemData { ItemData(cbor) } pub fn sort_keys(&mut self) -> MizeResult<()> { item_data_sort_keys(&mut self.0) } pub fn null() -> CborValue { CborValue::Null } pub fn parse>(value: T) -> ItemData { let value_str = value.into(); if value_str == "false" { return CborValue::Bool(false).into_item_data(); } if value_str == "false" { return CborValue::Bool(true).into_item_data(); } if let Ok(int) = value_str.parse::() { return int.into_item_data(); } return CborValue::Text(value_str).into_item_data(); } pub fn set_path(&mut self, path: P, value: D) -> MizeResult<()> { //trace!("[ {} ] ItemData::set_path()", "CALL".yellow()); let path = path.into_path(); let value = value.into_item_data(); item_data_set_path(&mut self.0, path, &value.0) } pub fn get_path(&self, path: P) -> MizeResult { let path = path.into_path(); Ok(item_data_get_path(&self.0, path)? .to_owned() .into_item_data()) } pub fn get_paths_recursive(&mut self) -> MizeResult> { let mut paths = Vec::new(); let mut path_accu = Vec::new(); get_paths(&mut self.0, &mut paths, &mut path_accu, true)?; Ok(paths) } pub fn get_paths(&mut self) -> MizeResult> { let mut paths = Vec::new(); let mut path_accu = Vec::new(); get_paths(&mut self.0, &mut paths, &mut path_accu, false)?; Ok(paths) } pub fn arr_nth(&self, index: usize) -> MizeResult { let array = self .0 .as_array() .ok_or(mize_err!("ItemData is not an array, cannot get element at index {}", index))?; let value = array .get(index) .ok_or(mize_err!("array index {} out of bounds (length: {})", index, array.len()))?; Ok(ItemData::from_cbor(value.clone())) } /// Try to get the native id from the `_native` field. /// Returns `Some(id)` if the field exists and is a valid u32 string. /// Returns `None` if the field doesn't exist or is not a valid id. fn try_get_native_id(&self) -> Option { let id_data = self.get_path("_native").ok()?; if id_data.cbor().is_null() { return None; } if let Some(integer) = id_data.cbor().as_integer() { if let Ok(val) = u32::try_from(integer) { return Some(val) } } return None; } pub fn deserialize(&self) -> MizeResult { let mut buffer = Vec::new(); into_writer(&self, &mut buffer)?; let cbor_val: T = from_reader(buffer.as_slice())?; Ok(cbor_val) } /// Get an immutable reference to the native value stored in this ItemData. /// /// If the ItemData has a `_native` field (stored via `from_native()`), returns a guard /// that takes the value out of the global registry and provides an immutable reference to it. /// /// Otherwise, for supported hardcoded types (String, bool, Vec, Vec, /// integer types, float types), constructs the value directly from the CBOR content, /// stores it in the registry, and returns a guard to it. /// /// The guard temporarily removes the value from the registry to allow the mutex lock to be /// dropped, improving concurrency. The value is automatically reinserted when the guard is dropped. pub fn native(&self) -> MizeResult> { let mize = get_global_mize()?; let mut native_values = mize.native_values.lock().map_err(|_| mize_err!("Failed to lock native_values"))?; let id = match self.try_get_native_id() { Some(id) => id, None => { // No _native field, try to construct from CBOR let value = try_construct_native::(self.cbor())? .ok_or_else(|| mize_err!( "ItemData has no _native field and type {} is not supported for direct construction from CBOR. Value: {}", std::any::type_name::(), self ))?; // Store in registry and use the new id let mut next_id = mize.next_native_value_id.lock() .map_err(|_| mize_err!("Failed to lock next_native_value_id"))?; let new_id = *next_id; *next_id += 1; native_values.insert(new_id, Box::new(value)); new_id } }; let boxed = native_values.remove(&id) .ok_or(mize_err!("No native value with id {}", id))?; let typed = boxed.downcast::() .map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::()))?; // Drop the mutex guard before returning - the value is now owned by the guard drop(native_values); Ok(NativeRefGuard { value: std::mem::ManuallyDrop::new(*typed), id, needs_reinsert: true, }) } /// Get an immutable reference to a native value of type `T`, using [`FromItemData`] /// to construct it when it is not already stored in the registry as a `T`. /// /// If the ItemData has a `_native` field (stored via `from_native()`) and the registry /// value is a `T`, returns a guard to it (the value is temporarily removed from the /// registry and reinserted when the guard is dropped). /// /// Otherwise, constructs the value via `T::from_itemdata(self)`, stores it in the /// registry, and returns a guard to it. /// i hate rust. (as you can't have a generic FromItemData impl and then more specific ones.) pub fn native_from(&self) -> MizeResult> { // Fast path: the registry already holds a T for this ItemData if let Some(id) = self.try_get_native_id() { let mize = get_global_mize()?; let mut native_values = mize.native_values.lock() .map_err(|_| mize_err!("Failed to lock native_values"))?; if native_values.get(&id).map(|b| b.downcast_ref::().is_some()).unwrap_or(false) { let boxed = native_values.remove(&id) .ok_or(mize_err!("No native value with id {}", id))?; let typed = boxed.downcast::() .map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::()))?; drop(native_values); return Ok(NativeRefGuard { value: std::mem::ManuallyDrop::new(*typed), id, needs_reinsert: true, }); } } // Fallback: let T construct itself from this ItemData (without holding the registry lock) let value = T::from_itemdata(self.clone())?; // Store in registry and hand out a guard to it let mize = get_global_mize()?; let mut native_values = mize.native_values.lock() .map_err(|_| mize_err!("Failed to lock native_values"))?; let new_id = { let mut next_id = mize.next_native_value_id.lock() .map_err(|_| mize_err!("Failed to lock next_native_value_id"))?; let new_id = *next_id; *next_id += 1; new_id }; native_values.insert(new_id, Box::new(value)); let boxed = native_values.remove(&new_id) .ok_or(mize_err!("No native value with id {}", new_id))?; let typed = boxed.downcast::() .map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::()))?; drop(native_values); Ok(NativeRefGuard { value: std::mem::ManuallyDrop::new(*typed), id: new_id, needs_reinsert: true, }) } /// Get a mutable reference to the native value stored in this ItemData. /// /// If the ItemData has a `_native` field (stored via `from_native()`), returns a guard /// that takes the value out of the global registry and provides a mutable reference to it. /// /// Otherwise, for supported hardcoded types (String, bool, Vec, Vec, /// integer types, float types), constructs the value directly from the CBOR content, /// stores it in the registry, and returns a mutable guard to it. /// /// The guard temporarily removes the value from the registry to allow the mutex lock to be /// dropped, improving concurrency. The value is automatically reinserted when the guard is dropped. pub fn native_mut(&self) -> MizeResult> { let mize = get_global_mize()?; let mut native_values = mize.native_values.lock().map_err(|_| mize_err!("Failed to lock native_values"))?; let id = match self.try_get_native_id() { Some(id) => id, None => { // No _native field, try to construct from CBOR let value = try_construct_native::(self.cbor())? .ok_or_else(|| mize_err!( "ItemData has no _native field and type {} is not supported for direct construction from CBOR. Value: {}", std::any::type_name::(), self ))?; // Store in registry and use the new id let mut next_id = mize.next_native_value_id.lock() .map_err(|_| mize_err!("Failed to lock next_native_value_id"))?; let new_id = *next_id; *next_id += 1; native_values.insert(new_id, Box::new(value)); new_id } }; let boxed = native_values.remove(&id) .ok_or(mize_err!("No native value with id {}", id))?; let typed = boxed.downcast::() .map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::()))?; // Drop the mutex guard before returning - the value is now owned by the guard drop(native_values); Ok(NativeMutRefGuard { value: std::mem::ManuallyDrop::new(*typed), id, needs_reinsert: true, }) } /// Get a mutable reference to a native value of type `T`, using [`FromItemData`] /// to construct it when it is not already stored in the registry as a `T`. /// /// If the ItemData has a `_native` field (stored via `from_native()`) and the registry /// value is a `T`, returns a mutable guard to it (the value is temporarily removed /// from the registry and reinserted when the guard is dropped). /// /// Otherwise, constructs the value via `T::from_itemdata(self)`, stores it in the /// registry, and returns a mutable guard to it. pub fn native_mut_from(&self) -> MizeResult> { // Fast path: the registry already holds a T for this ItemData if let Some(id) = self.try_get_native_id() { let mize = get_global_mize()?; let mut native_values = mize.native_values.lock() .map_err(|_| mize_err!("Failed to lock native_values"))?; if native_values.get(&id).map(|b| b.downcast_ref::().is_some()).unwrap_or(false) { let boxed = native_values.remove(&id) .ok_or(mize_err!("No native value with id {}", id))?; let typed = boxed.downcast::() .map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::()))?; drop(native_values); return Ok(NativeMutRefGuard { value: std::mem::ManuallyDrop::new(*typed), id, needs_reinsert: true, }); } } // Fallback: let T construct itself from this ItemData (without holding the registry lock) let value = T::from_itemdata(self.clone())?; // Store in registry and hand out a mutable guard to it let mize = get_global_mize()?; let mut native_values = mize.native_values.lock() .map_err(|_| mize_err!("Failed to lock native_values"))?; let new_id = { let mut next_id = mize.next_native_value_id.lock() .map_err(|_| mize_err!("Failed to lock next_native_value_id"))?; let new_id = *next_id; *next_id += 1; new_id }; native_values.insert(new_id, Box::new(value)); let boxed = native_values.remove(&new_id) .ok_or(mize_err!("No native value with id {}", new_id))?; let typed = boxed.downcast::() .map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::()))?; drop(native_values); Ok(NativeMutRefGuard { value: std::mem::ManuallyDrop::new(*typed), id: new_id, needs_reinsert: true, }) } /// Get a clone of the native value stored in this ItemData. /// /// If the ItemData has a `_native` field (stored via `from_native()`), clones the value /// from the global registry. /// /// Otherwise, for supported hardcoded types (String, bool, Vec, Vec, /// integer types, float types), constructs the value directly from the CBOR content. /// /// This is useful when you need to pass the value by ownership (e.g., as a function argument). pub fn native_clone(&self) -> MizeResult { // Try registry first if let Some(id) = self.try_get_native_id() { let mize = get_global_mize()?; let native_values = mize.native_values.lock() .map_err(|_| mize_err!("Failed to lock native_values"))?; let boxed = native_values.get(&id) .ok_or(mize_err!("No native value with id {}", id))?; let val = boxed.downcast_ref::() .ok_or(mize_err!("Failed to downcast native value to {}", std::any::type_name::()))?; return Ok(val.clone()); } // Fallback: construct directly from CBOR try_construct_native::(self.cbor())? .ok_or_else(|| mize_err!( "ItemData has no _native field and type {} is not supported for direct construction from CBOR. Value: {}", std::any::type_name::(), self )) } /// Take the native value from this ItemData, removing it from the registry. /// /// If the ItemData has a `_native` field (stored via `from_native()`), removes and /// returns the value from the global registry. /// /// Otherwise, for supported hardcoded types (String, bool, Vec, Vec, /// integer types, float types), constructs the value directly from the CBOR content. pub fn native_take(&self) -> MizeResult { // Try registry first if let Some(id) = self.try_get_native_id() { let mize = get_global_mize()?; let mut native_values = mize.native_values.lock() .map_err(|_| mize_err!("Failed to lock native_values"))?; let boxed = native_values.remove(&id) .ok_or(mize_err!("No native value with id {}", id))?; let typed = boxed.downcast::() .map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::()))?; return Ok(*typed); } // Fallback: construct directly from CBOR try_construct_native::(self.cbor())? .ok_or_else(|| mize_err!( "ItemData has no _native field and type {} is not supported for direct construction from CBOR. Value: {}", std::any::type_name::(), self )) } /// Take a native value of type `T` from this ItemData, using [`FromItemData`] as fallback. /// /// If the ItemData has a `_native` field (stored via `from_native()`) and the registry /// value is a `T`, removes and returns it from the global registry. /// /// Otherwise, constructs and returns the value via `T::from_itemdata(self)`. pub fn native_take_from(&self) -> MizeResult { // Try registry first: only take the value out if it actually is a T if let Some(id) = self.try_get_native_id() { let mize = get_global_mize()?; let mut native_values = mize.native_values.lock() .map_err(|_| mize_err!("Failed to lock native_values"))?; if native_values.get(&id).map(|b| b.downcast_ref::().is_some()).unwrap_or(false) { let boxed = native_values.remove(&id) .ok_or(mize_err!("No native value with id {}", id))?; let typed = boxed.downcast::() .map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::()))?; return Ok(*typed); } } // Fallback: let T construct itself from this ItemData T::from_itemdata(self.clone()) } } /// Try to construct a native value of type `T` from a `CborValue`. /// /// Supports hardcoded types: /// - `String` from `CborValue::Text` /// - `bool` from `CborValue::Bool` /// - `Vec` from `CborValue::Bytes` /// - `Vec` from `CborValue::Array` of `Text` /// - Integer types (`i8`, `i16`, `i32`, `i64`, `i128`, `isize`, /// `u8`, `u16`, `u32`, `u64`, `u128`, `usize`) from `CborValue::Integer` /// - Float types (`f32`, `f64`) from `CborValue::Float` or `CborValue::Integer` /// /// Returns `Ok(Some(value))` if `T` is a supported type and conversion succeeds. /// Returns `Ok(None)` if `T` is not a supported hardcoded type. /// Returns `Err` if `T` is supported but the CBOR value cannot be converted. fn try_construct_native(cbor: &CborValue) -> MizeResult> { let tid = TypeId::of::(); macro_rules! try_convert { ($target:ty, $conv:expr) => { if tid == TypeId::of::<$target>() { let val: $target = $conv?; // SAFETY: TypeId verified that $target == T, so the transmute is sound. return Ok(Some(*unsafe { std::mem::transmute::, Box>(Box::new(val)) })); } }; } // String from Text try_convert!(String, cbor.as_text() .map(|s| s.to_owned()) .ok_or_else(|| mize_err!("ItemData is not a Text value, cannot construct String")) ); // bool from Bool try_convert!(bool, cbor.as_bool() .ok_or_else(|| mize_err!("ItemData is not a Bool value, cannot construct bool")) ); // Vec from Bytes try_convert!(Vec, cbor.as_bytes() .map(|b| b.clone()) .ok_or_else(|| mize_err!("ItemData is not a Bytes value, cannot construct Vec")) ); // Vec from Array of Text try_convert!(Vec, { let arr = cbor.as_array() .ok_or_else(|| mize_err!("ItemData is not an Array value, cannot construct Vec"))?; arr.iter() .map(|v| { v.as_text() .map(|s| s.to_owned()) .ok_or_else(|| mize_err!("Array element is not a Text value, cannot construct Vec")) }) .collect::>>() }); // Integer types from Integer macro_rules! try_int { ($target:ty) => { try_convert!($target, { let int = cbor.as_integer() .ok_or_else(|| mize_err!("ItemData is not an Integer value, cannot construct {}", std::any::type_name::<$target>()))?; let i128_val: i128 = int.into(); <$target>::try_from(i128_val) .map_err(|_| mize_err!("Integer value {} does not fit in {}", i128_val, std::any::type_name::<$target>())) }); }; } try_int!(i8); try_int!(i16); try_int!(i32); try_int!(i64); try_int!(i128); try_int!(isize); try_int!(u8); try_int!(u16); try_int!(u32); try_int!(u64); try_int!(u128); try_int!(usize); // Float types from Float or Integer macro_rules! try_float { ($target:ty) => { try_convert!($target, { match cbor { CborValue::Float(f) => Ok(*f as $target), CborValue::Integer(i) => { let i128_val: i128 = (*i).into(); Ok(i128_val as $target) } _ => Err(mize_err!( "ItemData is neither Float nor Integer, cannot construct {}", std::any::type_name::<$target>() )), } }); }; } try_float!(f32); try_float!(f64); // Type not supported for direct construction Ok(None) } /// Guard that holds a native value taken from the registry. /// /// The value is temporarily removed from the registry to allow the mutex lock to be dropped. /// When the guard is dropped, the value is automatically reinserted into the registry. pub struct NativeRefGuard { value: std::mem::ManuallyDrop, id: u32, needs_reinsert: bool, } impl std::ops::Deref for NativeRefGuard { type Target = T; fn deref(&self) -> &Self::Target { &*self.value } } impl Drop for NativeRefGuard { fn drop(&mut self) { if self.needs_reinsert { let mize = get_global_mize().expect("Failed to get global mize in NativeRefGuard drop"); let mut native_values = mize.native_values.lock().expect("Failed to lock native_values in NativeRefGuard drop"); let value = unsafe { std::mem::ManuallyDrop::take(&mut self.value) }; native_values.insert(self.id, Box::new(value)); } else { unsafe { std::mem::ManuallyDrop::drop(&mut self.value) }; } } } /// Guard that holds a mutable native value taken from the registry. /// /// The value is temporarily removed from the registry to allow the mutex lock to be dropped. /// When the guard is dropped, the value is automatically reinserted into the registry. pub struct NativeMutRefGuard { value: std::mem::ManuallyDrop, id: u32, needs_reinsert: bool, } impl std::ops::Deref for NativeMutRefGuard { type Target = T; fn deref(&self) -> &Self::Target { &*self.value } } impl std::ops::DerefMut for NativeMutRefGuard { fn deref_mut(&mut self) -> &mut Self::Target { &mut *self.value } } impl Drop for NativeMutRefGuard { fn drop(&mut self) { if self.needs_reinsert { let mize = get_global_mize().expect("Failed to get global mize in NativeMutRefGuard drop"); let mut native_values = mize.native_values.lock().expect("Failed to lock native_values in NativeMutRefGuard drop"); let value = unsafe { std::mem::ManuallyDrop::take(&mut self.value) }; native_values.insert(self.id, Box::new(value)); } else { unsafe { std::mem::ManuallyDrop::drop(&mut self.value) }; } } } fn get_paths( val: &mut CborValue, paths: &mut Vec, path_accu: &mut Vec, recurse: bool, ) -> MizeResult<()> { let map = val .as_map_mut() .ok_or(mize_err!("ItemData is not map... when calling get_paths"))?; for (key, val) in map { let text = key .as_text() .ok_or(mize_err!("ItemData::get_paths but key is not of type text"))? .to_owned(); if recurse && val.is_map() { let mut path_accu = path_accu.clone(); path_accu.push(text); get_paths(val, paths, &mut path_accu, recurse)?; } else { let path = path_accu.join("/") + "/" + text.as_str(); paths.push(path); } } Ok(()) } impl Default for ItemData { fn default() -> Self { ItemData::new() } } pub fn data_from_string(data_string: String) -> MizeResult { let mut data = ItemData::new(); // in case it's just a string if !data_string.contains("=") { let cbor = CborValue::Text(data_string); return Ok(ItemData::from_cbor(cbor)); } for option in data_string.split(":") { //trace!("data_from_string: option: {}", option); if option == "".to_owned() { continue; } let path = option .split("=") .nth(0) .ok_or(MizeError::new().msg(format!( "Failed to parse Option: option '{}' has an empty path (thing before =)", option )))?; //trace!("data_from_string: path: {}", path); let value = option .split("=") .nth(1) .ok_or(MizeError::new().msg(format!( "Failed to parse Option: option '{}' has an empty value (thing after =)", option )))?; //trace!("data_from_string: value: {}", value); let mut path_vec = Vec::new(); path_vec.extend(path.split(".")); //trace!("data_from_string: path_vec: {:?}", path_vec); data.set_path(path_vec, ItemData::parse(value))?; } trace!("data_from_string: data: {}", data); return Ok(data); } pub fn item_data_get_path(data: &CborValue, path: Vec) -> MizeResult<&CborValue> { let mut path_iter = path.clone().into_iter(); let path_el = match path_iter.nth(0) { Some(val) => val, None => return Ok(data), // our base case }; let mut sub_data = &CborValue::Null; match data { CborValue::Map(map) => { for (key, val) in map { if let CborValue::Text(key_str) = key { if key_str == &path_el { sub_data = val; } } } } val => { return Err(MizeError::new() .msg(format!( "Failed to get path '{:?}' from ItemData, the data at '{}' is not a map", path, path_el )) .msg(format!("{:?} is: {:?}", path_el, val))); } }; item_data_get_path(sub_data, path_iter.collect()) } pub fn item_data_sort_keys(data: &mut CborValue) -> MizeResult<()> { // for a list, sort each item of the list if data.is_array() { for el in data.as_array_mut().unwrap().iter_mut() { item_data_sort_keys(el)? } } // sort the keys of a map if data.is_map() { let map: &mut Vec<(CborValue, CborValue)> = data.as_map_mut().unwrap(); //map.sort_by_key(|el| el.0); map.sort_by(|a, b| { match a.0.partial_cmp(&b.0) { Some(val) => val, None => { // just say the two values are equal, if they can't be ordered std::cmp::Ordering::Equal } } }); // also sort every sub value for (_key, val) in map { item_data_sort_keys(val)? } } Ok(()) } pub fn item_data_set_path( data: &mut CborValue, path: Vec, data_to_set: &CborValue, ) -> MizeResult<()> { //trace!("[ {} ] item_data_set_path()", "CALL".yellow()); //trace!("[ {} ] data: {}", "ARG".yellow(), data.clone().into_item_data()); //trace!("[ {} ] path: {:?}", "ARG".yellow(), path); //trace!("[ {} ] data_to_set: {}", "ARG".yellow(), data_to_set.clone().into_item_data()); let mut path_iter = path.clone().into_iter(); let path_el = match path_iter.nth(0) { Some(val) => val, None => { //trace!("item_data_set_path: base_case"); // our base case *data = data_to_set.to_owned(); return Ok(()); } }; match data { CborValue::Map(map) => { for (key, val) in &mut *map { if let CborValue::Text(key_str) = key { if key_str == &path_el { //trace!("item_data_set_path: setting key {key_str} in map with recursive call"); return item_data_set_path(val, path_iter.collect(), data_to_set); } } } // if we get here, that means, the key (which is the variable path_el) does not exist // in the map // so add it... but also with a recursive call which is needed, in case path_iter // longer than 1 //trace!("item_data_set_path: adding key {path_el} to map"); let mut inner_value = CborValue::Null; item_data_set_path(&mut inner_value, path_iter.collect(), data_to_set); map.push((CborValue::Text(path_el.clone()), inner_value)); return Ok(()); } CborValue::Null => { //trace!("item_data_set_path: CborValue::Null case"); let mut inner_value = CborValue::Null; item_data_set_path(&mut inner_value, path_iter.collect(), data_to_set); let map_vec = vec![(CborValue::Text(path_el), inner_value)]; *data = CborValue::Map(map_vec); return Ok(()); } val => { return Err(MizeError::new() .msg(format!( "Failed to get path '{:?}' from ItemData, the data at '{}' is not a map", path, path_el )) .msg(format!("{:?} is: {:?}", path_el, val))); } }; } pub fn item_data_merge(merge_into: &mut CborValue, other: &CborValue) { // needs to be recursive match (merge_into, other) { // if both are maps // merge the keys/vals in the maps and call recursively on them (CborValue::Map(merge_into_map), CborValue::Map(other_map)) => { // go through other_map and merge those keys/values to merge_into for (other_key, other_val) in other_map { // so find that same vale in merge_into, if exists recursively merge, else just add to // vec let mut found_value = CborValue::Null; let mut value_found = false; let mut new_value_for_merge_into: Vec<(CborValue, CborValue)> = Vec::new(); // here we collect the // new contents of the // map, to assign later // to merge_into_map for (merge_into_key, merge_into_val) in merge_into_map.clone() { if merge_into_key == *other_key { value_found = true; found_value = merge_into_val.to_owned(); // if we found it, also don't add it to new_value_for_merge_into } else { //if not found push to new_value_for_merge_into new_value_for_merge_into .push((merge_into_key.to_owned(), merge_into_val.to_owned())); } } if value_found { // we found it so merge and asign item_data_merge(&mut found_value, other_val); // to asign push to new_value_for_merge_into, because the old was removed already new_value_for_merge_into.push((other_key.to_owned(), found_value)); } else { // if not found, just add the other_key/val to new_value_for_merge_into new_value_for_merge_into.push((other_key.to_owned(), other_val.to_owned())); } // finally, we need to asign the new_value_for_merge_into into merge_into *merge_into_map = new_value_for_merge_into; } } // if other is Null, don't asign // this is gonna have repercussions.... because it's not clean behaviour (_merge_into, CborValue::Null) => {} // in any other case we just want to set merge_into to other // also this is the base case (merge_into, other) => { *merge_into = other.to_owned(); } } } #[instrument(name = "fn.get_raw_from_cbor")] pub fn get_raw_from_cbor<'a>(value: &'a CborValue, path: Vec<&String>) -> MizeResult<&'a [u8]> { trace!("[ {} ] value: {:?}", "ARG".yellow(), value); trace!("[ {} ] path: {:?}", "ARG".yellow(), path); let mut path_iter = path.clone().into_iter(); let path_el = match path_iter.nth(0) { Some(val) => val, None => { // our base case let _ret_val = match value { CborValue::Bytes(vec) => { trace!("[ {} ] ret value: {:?}", "RET".yellow(), &vec[..]); return Ok(&vec[..]); } CborValue::Text(string) => { trace!("[ {} ] ret value: {:?}", "RET".yellow(), string.as_bytes()); return Ok(string.as_bytes()); } other => { return Err(mize_err!( "path '{}' is empty and the value: '{:?}' is neither Bytes nor Text", path.iter().map(|p| p.as_str()).collect::>().join("."), other )); } }; } }; match value { CborValue::Bytes(vec) => { trace!("[ {} ] ret value: {:?}", "RET".yellow(), &vec[..]); return Ok(&vec[..]); } CborValue::Text(string) => { trace!("[ {} ] ret value: {:?}", "RET".yellow(), string.as_bytes()); return Ok(string.as_bytes()); } CborValue::Map(map) => { let mut inner_val: &CborValue = &CborValue::Null; let mut inner_val_found = false; // find the inner_val at path for (key, val) in map.into_iter() { if let CborValue::Text(key_text) = key { if key_text == path_el { inner_val = val; inner_val_found = true; } } } // if there is no value at that path if inner_val_found == false { return Err(MizeError::new() // jesus christ, this is a pfusch... .msg(format!( "get_raw_from_cbor: Path '{}' not Found", path.clone() .into_iter() .map(|v| v.to_owned()) .collect::>() .join("/") ))); } //path_iter.next(); let inner_path = path_iter.collect(); let ret_value = get_raw_from_cbor(inner_val, inner_path); trace!("[ {} ] ret value: {:?}", "RET".yellow(), ret_value); return ret_value; } _ => Err(MizeError::new().msg(format!( "get_raw_from_cbor: value is not a map, text or bytes ... value: {:?}", value ))), } } trait IntoPath { fn into_path(self) -> Vec; } pub trait IntoItemData { fn into_item_data(self) -> ItemData; } impl IntoPath for &str { fn into_path(self) -> Vec { if !self.contains('/') { return self.split(".").map(|v| v.to_owned()).collect(); } self.to_owned().split("/").map(|v| v.to_owned()).collect() } } impl IntoPath for usize { fn into_path(self) -> Vec { vec![format!("{}", self)] } } impl IntoPath for &[&str] { fn into_path(self) -> Vec { self.into_iter().map(|v| (*v).to_owned()).collect() } } impl IntoPath for Vec { fn into_path(self) -> Vec { self } } impl IntoPath for Vec<&str> { fn into_path(self) -> Vec { self.into_iter().map(|v| v.to_owned()).collect() } } impl IntoItemData for &str { fn into_item_data(self) -> ItemData { ItemData(self.into()) } } impl IntoItemData for String { fn into_item_data(self) -> ItemData { ItemData(self.into()) } } impl IntoItemData for &String { fn into_item_data(self) -> ItemData { ItemData(self.to_owned().into()) } } impl IntoItemData for Vec { fn into_item_data(self) -> ItemData { let array = self .into_iter() .map(|v| v.into_item_data().cbor().to_owned()) .collect::>(); ItemData(CborValue::Array(array)) } } impl IntoItemData for CborValue { fn into_item_data(self) -> ItemData { ItemData(self) } } impl IntoItemData for ItemData { fn into_item_data(self) -> ItemData { self } } impl IntoItemData for i128 { fn into_item_data(self) -> ItemData { ItemData(self.into()) } } impl IntoItemData for u32 { fn into_item_data(self) -> ItemData { ItemData(self.into()) } } impl IntoItemData for bool { fn into_item_data(self) -> ItemData { ItemData(self.into()) } } impl fmt::Display for ItemData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ItemData: "); let display_writer = DisplayWriter(f); self.0 .serialize(&mut serde_json::Serializer::pretty(display_writer)) .map_err(|_serde_err| std::fmt::Error) } } // thanks to: https://stackoverflow.com/a/61768916 struct DisplayWriter<'a, 'b>(&'a mut fmt::Formatter<'b>); impl<'a, 'b> io::Write for DisplayWriter<'a, 'b> { fn write(&mut self, bytes: &[u8]) -> std::result::Result { self.0 .write_str(&String::from_utf8_lossy(bytes)) .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?; Ok(bytes.len()) } fn flush(&mut self) -> std::result::Result<(), std::io::Error> { todo!() } } impl PartialEq for ItemData { fn eq(&self, other: &Self) -> bool { let mut buf: Vec = Vec::new(); let mut buf_other: Vec = Vec::new(); ciborium::into_writer(&self.0, &mut buf); ciborium::into_writer(&other.0, &mut buf_other); buf == buf_other } } impl Eq for ItemData {} pub fn set_config_from_data(mize: &mut Mize, mut data: ItemData) -> MizeResult<()> { let mut config_opts = mize.config_opts.lock().unwrap(); for path in data.get_paths_recursive()? { let mut conf_name = path.replace("/", "."); if conf_name.starts_with(".") { conf_name.remove(0); } let val = data.get_path(path.split("/").filter(|s| s != &"").collect::>())?; match config_opts.get_mut(&mize.clone().id_new_dots(&conf_name)?) { Some(opt) => { opt.val = Some(val); } None => { config_opts.insert( mize.clone().id_new_dots(&conf_name)?, ConfigOpt { name: conf_name, val: Some(val), fun: None, deps: Vec::new(), }, ); } }; } Ok(()) } #[cfg(test)] mod tests { use super::*; use crate::set_global_mize; use crate::instance::Mize; use std::sync::{Mutex, MutexGuard}; /// Mutex to serialize tests that use the global Mize instance. static TEST_MUTEX: Mutex<()> = Mutex::new(()); fn setup() -> MizeResult> { let guard = TEST_MUTEX.lock().map_err(|_| mize_err!("Failed to lock test mutex"))?; let mize = Mize::empty()?; set_global_mize(mize)?; Ok(guard) } #[test] fn test_native_clone_string_from_cbor() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_string("hello world"); let s: String = data.native_clone()?; assert_eq!(s, "hello world"); Ok(()) } #[test] fn test_native_take_string_from_cbor() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_string("take me"); let s: String = data.native_take()?; assert_eq!(s, "take me"); Ok(()) } #[test] fn test_native_string_from_cbor() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_string("guard test"); let guard = data.native::()?; assert_eq!(&*guard, "guard test"); Ok(()) } #[test] fn test_native_mut_string_from_cbor() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_string("mutable"); { let mut guard = data.native_mut::()?; guard.push_str(" updated"); assert_eq!(&*guard, "mutable updated"); } // Note: each call to native/native_mut on an ItemData without _native // constructs a fresh value from the CBOR content. The ItemData itself // is not modified, so the original CBOR value is used each time. let guard2 = data.native::()?; assert_eq!(&*guard2, "mutable"); Ok(()) } #[test] fn test_native_clone_bool_from_cbor() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_cbor(CborValue::Bool(true)); let b: bool = data.native_clone()?; assert_eq!(b, true); Ok(()) } #[test] fn test_native_clone_i32_from_cbor() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_cbor(CborValue::Integer(42.into())); let i: i32 = data.native_clone()?; assert_eq!(i, 42); Ok(()) } #[test] fn test_native_clone_u64_from_cbor() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_cbor(CborValue::Integer(123456789u64.into())); let u: u64 = data.native_clone()?; assert_eq!(u, 123456789); Ok(()) } #[test] fn test_native_clone_i128_from_cbor() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_cbor(CborValue::from(9999999999i128)); let i: i128 = data.native_clone()?; assert_eq!(i, 9999999999); Ok(()) } #[test] fn test_native_clone_f64_from_cbor() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_cbor(CborValue::Float(3.14159)); let f: f64 = data.native_clone()?; assert!((f - 3.14159).abs() < 0.00001); Ok(()) } #[test] fn test_native_clone_f64_from_integer() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_cbor(CborValue::Integer(42.into())); let f: f64 = data.native_clone()?; assert_eq!(f, 42.0); Ok(()) } #[test] fn test_native_clone_vec_string_from_cbor() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_cbor(CborValue::Array(vec![ CborValue::Text("a".to_string()), CborValue::Text("b".to_string()), CborValue::Text("c".to_string()), ])); let v: Vec = data.native_clone()?; assert_eq!(v, vec!["a".to_string(), "b".to_string(), "c".to_string()]); Ok(()) } #[test] fn test_native_clone_vec_u8_from_cbor() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_cbor(CborValue::Bytes(vec![1, 2, 3, 4, 5])); let v: Vec = data.native_clone()?; assert_eq!(v, vec![1, 2, 3, 4, 5]); Ok(()) } #[test] fn test_native_clone_usize_from_cbor() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_cbor(CborValue::Integer(99.into())); let u: usize = data.native_clone()?; assert_eq!(u, 99); Ok(()) } #[test] fn test_native_registry_still_works() -> MizeResult<()> { let _guard = setup()?; // Store a custom type in the registry let data = ItemData::from_native("registry value".to_string())?; let s: String = data.native_clone()?; assert_eq!(s, "registry value"); Ok(()) } #[test] fn test_native_take_registry() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_native(777i32)?; let i: i32 = data.native_take()?; assert_eq!(i, 777); // Second take should fail (already removed from registry) let result: MizeResult = data.native_take(); assert!(result.is_err()); Ok(()) } struct TestNative { value: i32, } impl FromItemData for TestNative { fn from_itemdata(data: ItemData) -> MizeResult { let value = data.get_path("value")?.value_u32()? as i32; Ok(TestNative { value }) } } struct IdWrapper { native_id: u32, } impl FromItemData for IdWrapper { fn from_itemdata(data: ItemData) -> MizeResult { Ok(IdWrapper { native_id: data.get_path("_native")?.value_u32()? }) } } #[test] fn test_native_take_from_registry() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_native(TestNative { value: 5 })?; let v: TestNative = data.native_take_from()?; assert_eq!(v.value, 5); Ok(()) } #[test] fn test_native_take_from_itemdata() -> MizeResult<()> { let _guard = setup()?; let mut data = ItemData::new(); data.set_path("value", 42u32)?; let v: TestNative = data.native_take_from()?; assert_eq!(v.value, 42); Ok(()) } #[test] fn test_native_take_from_wrapper_keeps_registry_value() -> MizeResult<()> { let _guard = setup()?; // registry holds a u32, but we take an IdWrapper which builds itself from the _native field let data = ItemData::from_native(777u32)?; let expected_id = data.get_path("_native")?.value_u32()?; let w: IdWrapper = data.native_take_from()?; assert_eq!(w.native_id, expected_id); // the u32 was not consumed and can still be taken let v: u32 = data.native_take()?; assert_eq!(v, 777); Ok(()) } #[test] fn test_native_from_registry() -> MizeResult<()> { let _guard = setup()?; let data = ItemData::from_native(TestNative { value: 9 })?; let guard = data.native_from::()?; assert_eq!((*guard).value, 9); Ok(()) } #[test] fn test_native_mut_from_itemdata() -> MizeResult<()> { let _guard = setup()?; let mut data = ItemData::new(); data.set_path("value", 1u32)?; { let mut guard = data.native_mut_from::()?; (*guard).value += 10; assert_eq!((*guard).value, 11); } // a fresh guard is constructed from the CBOR content again let guard2 = data.native_from::()?; assert_eq!((*guard2).value, 1); Ok(()) } #[test] fn test_native_clone_unsupported_type_fails() -> MizeResult<()> { let _guard = setup()?; // HashMap is not a supported hardcoded type let data = ItemData::from_string("test"); let result: MizeResult> = data.native_clone(); assert!(result.is_err()); Ok(()) } #[test] fn test_native_clone_wrong_type_fails() -> MizeResult<()> { let _guard = setup()?; // ItemData has Text, but we ask for bool let data = ItemData::from_string("not a bool"); let result: MizeResult = data.native_clone(); assert!(result.is_err()); Ok(()) } #[test] fn test_native_clone_integer_overflow_fails() -> MizeResult<()> { let _guard = setup()?; // i128 value too large for u8 let data = ItemData::from_cbor(CborValue::Integer(300.into())); let result: MizeResult = data.native_clone(); assert!(result.is_err()); Ok(()) } }