1687 lines
60 KiB
Rust
1687 lines
60 KiB
Rust
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<Vec<u8>> {
|
|
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<String> {
|
|
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<ItemData> {
|
|
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 = <Box<[std::string::String]> as Clone>::clone(&id
|
|
.path)
|
|
.into_iter()
|
|
.skip(2)
|
|
.map(|v| v.to_owned())
|
|
.collect::<Vec<String>>()
|
|
.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<V: Into<ItemData> + 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 = <Box<[std::string::String]> as Clone>::clone(&self
|
|
.id()
|
|
.path)
|
|
.into_iter()
|
|
.skip(1)
|
|
.map(|v| v.to_owned())
|
|
.collect::<Vec<String>>()
|
|
.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<ItemData> {
|
|
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<String> {
|
|
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<T: Serialize>(val: T) -> MizeResult<ItemData> {
|
|
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: <id>
|
|
pub fn from_native<T: Any + Send + Sync + 'static>(val: T) -> MizeResult<ItemData> {
|
|
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<String> {
|
|
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<config::MizeFun> {
|
|
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<ItemData> {
|
|
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<ItemData> {
|
|
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<u32> {
|
|
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<bool> {
|
|
match self.cbor() {
|
|
CborValue::Bool(val) => Ok(*val),
|
|
_ => Err(mize_err!("this ItemData has no string value")),
|
|
}
|
|
}
|
|
pub fn as_bool(self) -> Option<bool> {
|
|
match self.cbor() {
|
|
CborValue::Bool(val) => Some(*val),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn from_string<S: Into<String>>(into_string: S) -> ItemData {
|
|
let string = into_string.into();
|
|
let data = CborValue::Text(string);
|
|
return ItemData(data);
|
|
}
|
|
|
|
pub fn to_json(self) -> MizeResult<String> {
|
|
let mut result: Vec<u8> = 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<ItemData> {
|
|
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<T: Into<String>>(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::<i128>() {
|
|
return int.into_item_data();
|
|
}
|
|
return CborValue::Text(value_str).into_item_data();
|
|
}
|
|
|
|
pub fn set_path<P: IntoPath, D: IntoItemData>(&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<P: IntoPath>(&self, path: P) -> MizeResult<ItemData> {
|
|
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<Vec<String>> {
|
|
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<Vec<String>> {
|
|
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<ItemData> {
|
|
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<u32> {
|
|
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<T: Serialize + DeserializeOwned>(&self) -> MizeResult<T> {
|
|
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<String>, Vec<u8>,
|
|
/// 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<T: Any + Send + Sync + 'static>(&self) -> MizeResult<NativeRefGuard<T>> {
|
|
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::<T>(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::<T>(),
|
|
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::<T>()
|
|
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
|
|
|
// 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<T: FromItemData + Any + Send + Sync + 'static>(&self) -> MizeResult<NativeRefGuard<T>> {
|
|
// 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::<T>().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::<T>()
|
|
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
|
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::<T>()
|
|
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
|
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<String>, Vec<u8>,
|
|
/// 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<T: Any + Send + Sync + 'static>(&self) -> MizeResult<NativeMutRefGuard<T>> {
|
|
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::<T>(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::<T>(),
|
|
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::<T>()
|
|
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
|
|
|
// 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<T: FromItemData + Any + Send + Sync + 'static>(&self) -> MizeResult<NativeMutRefGuard<T>> {
|
|
// 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::<T>().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::<T>()
|
|
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
|
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::<T>()
|
|
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
|
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<String>, Vec<u8>,
|
|
/// 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<T: Any + Send + Sync + Clone + 'static>(&self) -> MizeResult<T> {
|
|
// 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::<T>()
|
|
.ok_or(mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
|
return Ok(val.clone());
|
|
}
|
|
|
|
// Fallback: construct directly from CBOR
|
|
try_construct_native::<T>(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::<T>(),
|
|
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<String>, Vec<u8>,
|
|
/// integer types, float types), constructs the value directly from the CBOR content.
|
|
pub fn native_take<T: Any + Send + Sync + 'static>(&self) -> MizeResult<T> {
|
|
// 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::<T>()
|
|
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
|
return Ok(*typed);
|
|
}
|
|
|
|
// Fallback: construct directly from CBOR
|
|
try_construct_native::<T>(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::<T>(),
|
|
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<T: FromItemData + Any + Send + Sync + 'static>(&self) -> MizeResult<T> {
|
|
// 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::<T>().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::<T>()
|
|
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
|
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<u8>` from `CborValue::Bytes`
|
|
/// - `Vec<String>` 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<T: Any + Send + Sync + 'static>(cbor: &CborValue) -> MizeResult<Option<T>> {
|
|
let tid = TypeId::of::<T>();
|
|
|
|
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<$target>, Box<T>>(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<u8> from Bytes
|
|
try_convert!(Vec<u8>,
|
|
cbor.as_bytes()
|
|
.map(|b| b.clone())
|
|
.ok_or_else(|| mize_err!("ItemData is not a Bytes value, cannot construct Vec<u8>"))
|
|
);
|
|
|
|
// Vec<String> from Array of Text
|
|
try_convert!(Vec<String>, {
|
|
let arr = cbor.as_array()
|
|
.ok_or_else(|| mize_err!("ItemData is not an Array value, cannot construct Vec<String>"))?;
|
|
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<String>"))
|
|
})
|
|
.collect::<MizeResult<Vec<String>>>()
|
|
});
|
|
|
|
// 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<T: Any + Send + Sync + 'static> {
|
|
value: std::mem::ManuallyDrop<T>,
|
|
id: u32,
|
|
needs_reinsert: bool,
|
|
}
|
|
|
|
impl<T: Any + Send + Sync + 'static> std::ops::Deref for NativeRefGuard<T> {
|
|
type Target = T;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&*self.value
|
|
}
|
|
}
|
|
|
|
impl<T: Any + Send + Sync + 'static> Drop for NativeRefGuard<T> {
|
|
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<T: Any + Send + Sync + 'static> {
|
|
value: std::mem::ManuallyDrop<T>,
|
|
id: u32,
|
|
needs_reinsert: bool,
|
|
}
|
|
|
|
impl<T: Any + Send + Sync + 'static> std::ops::Deref for NativeMutRefGuard<T> {
|
|
type Target = T;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&*self.value
|
|
}
|
|
}
|
|
|
|
impl<T: Any + Send + Sync + 'static> std::ops::DerefMut for NativeMutRefGuard<T> {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut *self.value
|
|
}
|
|
}
|
|
|
|
impl<T: Any + Send + Sync + 'static> Drop for NativeMutRefGuard<T> {
|
|
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<String>,
|
|
path_accu: &mut Vec<String>,
|
|
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<ItemData> {
|
|
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<String>) -> 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<String>,
|
|
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::<Vec<_>>().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::<Vec<String>>()
|
|
.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<String>;
|
|
}
|
|
|
|
pub trait IntoItemData {
|
|
fn into_item_data(self) -> ItemData;
|
|
}
|
|
|
|
impl IntoPath for &str {
|
|
fn into_path(self) -> Vec<String> {
|
|
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<String> {
|
|
vec![format!("{}", self)]
|
|
}
|
|
}
|
|
|
|
impl IntoPath for &[&str] {
|
|
fn into_path(self) -> Vec<String> {
|
|
self.into_iter().map(|v| (*v).to_owned()).collect()
|
|
}
|
|
}
|
|
impl IntoPath for Vec<String> {
|
|
fn into_path(self) -> Vec<String> {
|
|
self
|
|
}
|
|
}
|
|
impl IntoPath for Vec<&str> {
|
|
fn into_path(self) -> Vec<String> {
|
|
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<String> {
|
|
fn into_item_data(self) -> ItemData {
|
|
let array = self
|
|
.into_iter()
|
|
.map(|v| v.into_item_data().cbor().to_owned())
|
|
.collect::<Vec<_>>();
|
|
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<usize, std::io::Error> {
|
|
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<u8> = Vec::new();
|
|
let mut buf_other: Vec<u8> = 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::<Vec<&str>>())?;
|
|
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<MutexGuard<'static, ()>> {
|
|
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::<String>()?;
|
|
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::<String>()?;
|
|
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::<String>()?;
|
|
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<String> = 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<u8> = 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<i32> = data.native_take();
|
|
assert!(result.is_err());
|
|
Ok(())
|
|
}
|
|
|
|
struct TestNative {
|
|
value: i32,
|
|
}
|
|
|
|
impl FromItemData for TestNative {
|
|
fn from_itemdata(data: ItemData) -> MizeResult<Self> {
|
|
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<Self> {
|
|
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::<TestNative>()?;
|
|
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::<TestNative>()?;
|
|
(*guard).value += 10;
|
|
assert_eq!((*guard).value, 11);
|
|
}
|
|
// a fresh guard is constructed from the CBOR content again
|
|
let guard2 = data.native_from::<TestNative>()?;
|
|
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<std::collections::HashMap<String, String>> = 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<bool> = 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<u8> = data.native_clone();
|
|
assert!(result.is_err());
|
|
Ok(())
|
|
}
|
|
}
|