diff --git a/Cargo.lock b/Cargo.lock index 42b0b0d..1c713de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1108,6 +1108,7 @@ dependencies = [ "libloading", "nix", "serde", + "serde-wasm-bindgen", "serde_json", "sysinfo", "tokio", @@ -1528,6 +1529,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" version = "1.0.203" diff --git a/Cargo.toml b/Cargo.toml index 8a8b273..4fcd573 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ required-features = [ "os-target" ] we_alloc = [] default = [ "os-target" ] os-target = [ "clap", "home", "nix", "sysinfo", "ciborium/default", "tracing-subscriber", "async", "tracing-subscriber/env-filter", "libloading", "ciborium-io", "tracing-core" ] -wasm-target = [ "wasm-bindgen", "console_error_panic_hook", "wasm-bindgen-cli", "web-sys", "web-sys/Worker", "web-sys/Window", "web-sys/WorkerOptions", "web-sys/WorkerType", "we_alloc" ] +wasm-target = [ "wasm-bindgen", "console_error_panic_hook", "wasm-bindgen-cli", "web-sys", "web-sys/Worker", "web-sys/Window", "web-sys/WorkerOptions", "web-sys/WorkerType", "serde-wasm-bindgen" ] async = ["tokio/net", "tokio", "tokio/rt-multi-thread", "tokio/io-util"] @@ -48,6 +48,7 @@ wasm-bindgen = { version = "0.2.84", optional = true } console_error_panic_hook = { version = "0.1.7", optional = true } wasm-bindgen-cli = { version = "0.2.92", optional = true } web-sys = { version = "0.3.70", optional = true } +serde-wasm-bindgen = { version = "0.6.5", optional = true } # os dependencies clap = { version = "4.4.11", features = ["cargo"], optional = true } diff --git a/src/core/id.rs b/src/core/id.rs index f6e9196..a60a598 100644 --- a/src/core/id.rs +++ b/src/core/id.rs @@ -51,6 +51,15 @@ impl MizeId { self.path.clone() } + pub fn after_store_part(&self) -> Vec { + let tmp = self.path(); + let mut path_iter = tmp.into_iter(); + path_iter.next(); + let new_path: Vec = path_iter.map(|v| v.to_owned()).collect(); + + return new_path; + } + pub fn namespace(&self) -> Namespace { self.namespace.clone() } @@ -128,3 +137,46 @@ impl IntoMizeId for MizeId { //} +#[cfg(test)] +mod test { + use crate::instance::Instance; + + use super::*; + + #[test] + fn test_id() -> MizeResult<()> { + let instance = Instance::empty()?; + + let id_one = instance.new_id("0/config/hi")?; + assert_eq!(instance.get_namespace()?, id_one.namespace()); + assert_eq!("0", id_one.store_part()); + assert_eq!(vec!["config".to_owned(), "hi".to_owned()], id_one.after_store_part()); + + let id_two = instance.new_id("test.ns:hello/config/hi")?; + assert_eq!(instance.namespace_from_string("test.ns".to_owned())?, id_two.namespace()); + assert_eq!("hello", id_two.store_part()); + assert_eq!(vec!["config".to_owned(), "hi".to_owned()], id_one.after_store_part()); + + let id_three = instance.new_id("0/")?; + assert_eq!(instance.get_namespace()?, id_three.namespace()); + assert_eq!("0", id_three.store_part()); + assert_eq!(Vec::::new(), id_three.after_store_part()); + + let id_four = instance.new_id("test.ns:0/")?; + assert_eq!(instance.namespace_from_string("test.ns".to_owned())?, id_four.namespace()); + assert_eq!("0", id_four.store_part()); + assert_eq!(Vec::::new(), id_four.after_store_part()); + + let id_fife = instance.new_id("0")?; + assert_eq!(instance.get_namespace()?, id_fife.namespace()); + assert_eq!("0", id_fife.store_part()); + assert_eq!(Vec::::new(), id_fife.after_store_part()); + + //TODO: add the parsing form mize:// urls.... + + + Ok(()) + } + + +} diff --git a/src/core/instance/mod.rs b/src/core/instance/mod.rs index bc867fd..f29e4c8 100644 --- a/src/core/instance/mod.rs +++ b/src/core/instance/mod.rs @@ -41,6 +41,9 @@ pub mod updater; pub mod msg_thread; pub mod module; +#[cfg(test)] +mod tests; + static MSG_CHANNEL_SIZE: usize = 200; static BUILD_TIME_CONFIG: &str = include_str!(std::env!("MIZE_BUILD_CONFIG")); @@ -267,7 +270,7 @@ impl Instance { } pub fn id_from_string(&self, string: String) -> MizeResult { - let vec_string: Vec = string.split("/").map(|v| v.to_owned()).collect(); + let vec_string: Vec = string.split("/").map(|v| v.to_owned()).filter(|el| el.as_str() != "").collect(); self.id_from_vec_string(vec_string) } @@ -409,27 +412,6 @@ impl Instance { debug!("spawning thread: {}", name_to_move); let my_thread_id = my_thread_id_no_mutex_guard; - - #[cfg(feature = "wasm-target")] - unsafe { -// console_log macro -use wasm_bindgen::prelude::*; -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_namespace = console)] - fn log(s: &str); -} -macro_rules! console_log { - // Note that this is using the `log` function imported above during - // `bare_bones` - ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) -} -//end of console_log macro - console_log!("in other threadddddddddddddd"); - } - - - func()?; let mut threads_inner = thread_mutex.lock()?; diff --git a/src/core/instance/tests.rs b/src/core/instance/tests.rs new file mode 100644 index 0000000..ab6684e --- /dev/null +++ b/src/core/instance/tests.rs @@ -0,0 +1,148 @@ + +use tracing_subscriber::registry::Data; + +use crate::item::IntoItemData; + +use super::*; + +#[test] +fn test_set_sub_path() -> MizeResult<()> { + let instance = Instance::empty()?; + + let data = ItemData::from_toml(r#" + [config] + hi = "hello from config/hi" + + [config.test] + inner = "inner" + "#)?; + + let data_two = ItemData::from_toml(r#" + [config] + hi = "hello from config/hi" + + [config.test] + inner = "new inner" + "#)?; + + let item = instance.new_item()?; + instance.set_blocking(item.id(), data.clone())?; + + assert_eq!(item.as_data_full()?, data); + + // try to set a sub path of item + instance.set(vec![item.id().store_part(), "config", "test", "inner"], "hello".into_item_data())?; + + // the contents of item should then be like data_two + assert_eq!(instance.get(vec![item.id().store_part(), "config", "test", "inner"])?.as_data_full()?, ItemData::from_string("hello")); + assert_eq!(item.as_data_full()?, data_two); + + Ok(()) +} + +#[test] +fn test_itemdata_set_path() -> MizeResult<()> { + let mut data = ItemData::from_toml(r#" + [config] + hi = "hello from config/hi" + + [config.test] + inner = "inner" + "#)?; + + let data_two = ItemData::from_toml(r#" + [config] + hi = "hello from config/hi" + + [config.test] + inner = "new inner" + "#)?; + + data.set_path(vec!["config", "test", "inner"], ItemData::from_string("new inner"))?; + + assert_eq!(data, data_two); + + Ok(()) +} + +#[test] +fn test_itemdata_merge() -> MizeResult<()> { + let mut data = ItemData::from_toml(r#" + [config] + hi = "hello from config/hi" + + [config.test] + inner = "inner" + "#)?; + + let data_two = ItemData::from_toml(r#" + [config] + hi = "hello from config/hi" + + [config.test] + inner = "new inner" + "#)?; + + assert_eq!(data.get_path(vec!["config", "test", "inner"])?, ItemData::from_string("inner")); + + data.merge(data_two.clone()); + + assert_eq!(data, data_two); + + assert_eq!(data.get_path(vec!["config", "test", "inner"])?, ItemData::from_string("new inner")); + + Ok(()) +} + +#[test] +fn test_item_merge() -> MizeResult<()> { + let instance = Instance::empty()?; + + let mut item = instance.new_item()?; + + // here the item should contain "empty" data + assert_eq!(item.as_data_full()?, ItemData::new()); + + let data = ItemData::from_toml(r#" + [config] + hi = "hello from config/hi" + + [config.test] + inner = "inner" + "#)?; + + let data_two = ItemData::from_toml(r#" + [config] + hi = "hello from config/hi" + + [config.test] + inner = "new inner" + "#)?; + + item.merge(data.clone())?; + + // now it should contain data + assert_eq!(item.as_data_full()?, data); + + let mut sub_item = instance.get(vec![item.id().store_part(), "config", "test", "inner"])?; + + // the sub item should have the string "inner" + assert_eq!(sub_item.as_data_full()?, ItemData::from_string("inner")); + + sub_item.merge(ItemData::from_string("new inner")); + + assert_eq!(sub_item.as_data_full()?, ItemData::from_string("new inner")); + + // also the item should be updated + assert_eq!(item.as_data_full()?, data_two); + + + Ok(()) +} + +#[test] +#[should_panic(expected = "correct panic")] +fn test_cant_set_non_existent_item() -> () { + let instance = Instance::empty().expect("wrong panic"); + instance.set_blocking("5", "hello world".into_item_data()).expect("correct panic"); +} diff --git a/src/core/instance/updater.rs b/src/core/instance/updater.rs index b72c549..0c040b2 100644 --- a/src/core/instance/updater.rs +++ b/src/core/instance/updater.rs @@ -64,8 +64,9 @@ macro_rules! console_log { ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) } //end of console_log macro - console_log!("handle operation...."); + console_log!("handle operation....: {:?}", operation); } + match operation { Operation::Set(id, value, maybe_conn) => { let item_data: ItemData = value.to_owned(); @@ -81,7 +82,7 @@ macro_rules! console_log { }; for sub in vec.iter_mut() { - // don't handle sub of type connection, in case the update comes from this + // don't handle sub of type connection, in case the update comes from this // connection if let Some(conn) = maybe_conn { if let Subscription::Connection(conn2) = sub { diff --git a/src/core/item.rs b/src/core/item.rs index 9e948d2..4f57ebe 100644 --- a/src/core/item.rs +++ b/src/core/item.rs @@ -110,6 +110,7 @@ impl Item<'_> { pub fn merge + Debug >(&mut self, mut 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()); @@ -173,6 +174,10 @@ impl ItemData { &self.0 } + pub fn from_cbor(cbor: CborValue) -> ItemData { + ItemData (cbor) + } + pub fn null() -> CborValue { CborValue::Null } @@ -244,6 +249,12 @@ pub fn item_data_set_path(data: &mut CborValue, path: Vec, data_to_set: //trace!("[ {} ] path: {:?}", "ARG".yellow(), path); //trace!("[ {} ] data_to_set: {}", "ARG".yellow(), data_to_set.clone().into_item_data()); + test_println!("###########################"); + test_println!("data: {}", ItemData::from_cbor(data.clone())); + test_println!("path: {:?}", path); + test_println!("data_to_set: {}", ItemData::from_cbor(data_to_set.clone())); + test_println!("###########################"); + let mut path_iter = path.clone().into_iter(); let path_el = match path_iter.nth(0) { Some(val) => val, @@ -474,7 +485,7 @@ impl IntoItemData for i128 { impl fmt::Display for ItemData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "\nItemData: "); + 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) @@ -495,3 +506,17 @@ impl<'a, 'b> io::Write for DisplayWriter<'a, 'b> { 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 {} + + diff --git a/src/core/memstore.rs b/src/core/memstore.rs index c50c098..764b2de 100644 --- a/src/core/memstore.rs +++ b/src/core/memstore.rs @@ -12,6 +12,7 @@ use crate::error::{MizeError, MizeResult, IntoMizeResult}; use crate::item::{Item, ItemData}; use crate::instance::Instance; use crate::item::get_raw_from_cbor; +use crate::mize_err; #[derive(Clone, Debug)] pub struct MemStore { @@ -30,7 +31,18 @@ impl Store for MemStore { fn set(&self, id: MizeId, data: ItemData) -> MizeResult<()> { let mut inner = self.inner.lock()?; - inner.map.insert(id_to_u64(id)?, data); + if let Some(old_data) = inner.map.get(&id_to_u64(id.clone())?) { + //if there is already data there, set at the correct path + let mut old_data = old_data.to_owned(); + let path = id.after_store_part(); + old_data.set_path(path, data)?; + inner.map.insert(id_to_u64(id)?, old_data); + + } else { + // if no data exists for that store_part, just insert + inner.map.insert(id_to_u64(id)?, data); + } + return Ok(()) } fn get_links(&self, item: Item) -> MizeResult> { @@ -76,12 +88,32 @@ impl Store for MemStore { None => ItemData::new(), }; - let tmp = id.path(); - let mut path_iter = tmp.into_iter(); - path_iter.next(); - let new_path: Vec = path_iter.map(|v| v.to_owned()).collect(); + let new_path = id.after_store_part(); let ret_data = data.get_path(new_path)?; + + + #[cfg(feature = "wasm-target")] + unsafe { +// console_log macro +use wasm_bindgen::prelude::*; +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); +} +macro_rules! console_log { + // Note that this is using the `log` function imported above during + // `bare_bones` + ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) +} +//end of console_log macro + console_log!("ret_data in data full in memstore: {:?}", ret_data); + } + + + + return Ok(ret_data); } fn id_iter(&self) -> MizeResult { diff --git a/src/lib.rs b/src/lib.rs index 8d6fc3d..6013a3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,34 @@ #![ allow( warnings ) ] -#[no_mangle] -pub fn main() { - println!("hello from main fn"); +static PROTO_VERSION: u8 = 1; + +#[macro_export] +macro_rules! test_println { + ($($arg:tt)*) => { + + #[cfg(test)] + print!("[[ {} ]] ", <&str as colored::Colorize>::blue("TEST")); + + #[cfg(test)] + print!($($arg)*); + + #[cfg(test)] + print!("\n"); + }; } -static PROTO_VERSION: u8 = 1; +#[macro_export] +macro_rules! test_print { + ($($arg:tt)*) => { + + #[cfg(test)] + print!("[[ {} ]] ", <&str as colored::Colorize>::blue("TEST")); + + #[cfg(test)] + print!($($arg)*); + }; +} // the core part has the code that can run on any platform mod core { diff --git a/src/platform/wasm/mod.rs b/src/platform/wasm/mod.rs index 948d439..9e731fb 100644 --- a/src/platform/wasm/mod.rs +++ b/src/platform/wasm/mod.rs @@ -1,6 +1,8 @@ use std::ptr::NonNull; use std::panic; use web_sys::js_sys; +use crate::id::MizeId; +use crate::item::Item; use crate::platform::wasm::js_sys::Function; use web_sys::{WorkerOptions, WorkerType}; use web_sys::Worker; @@ -37,6 +39,8 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; // A function imitating `std::thread::spawn`. // thanks to: https://www.tweag.io/blog/2022-11-24-wasm-threads-and-messages/ pub fn wasm_spawn(f: impl FnOnce() -> MizeResult<()> + Send + 'static) -> MizeResult<()> { + console_log!("no threading in the browser....."); + return Ok(()); // to get window.mize_worker_url, which is set by some js code, which gets "compiled" by a // bundler, which sets window.mize_worker_url to the url of ./npm_pkg/worker.js @@ -118,13 +122,13 @@ pub struct JsInstance { inner: NonNull, } -/* + #[wasm_bindgen] -pub struct JsModule { - inner: NonNull>, - //inner: *mut Mme, +pub struct JsItemHandle { + instance: NonNull, + id: MizeId, } -*/ + #[wasm_bindgen] impl JsInstance { @@ -144,11 +148,44 @@ impl JsInstance { } #[wasm_bindgen] - pub unsafe fn test(&mut self) -> () { - console_log!("hiiiiiiiiiiiiiiii from test"); - self.inner.as_mut().set("0/config/hello", "hello world".into_item_data()); + pub unsafe fn set(&mut self, id: String, value: String) -> () { + let data = value.into_item_data(); + console_log!("data in set: {}", data); + self.inner.as_mut().set_blocking(id, data); } - //#[wasm_bindgen] + #[wasm_bindgen] + pub unsafe fn get_handle(&mut self, id: String) -> MizeResult { + let item = self.inner.as_mut().get(id)?; + Ok(JsItemHandle { instance: self.inner, id: item.id()}) + } +} + + +#[wasm_bindgen] +impl JsItemHandle { + + #[wasm_bindgen] + pub unsafe fn value_string(&mut self) -> MizeResult { + let item = self.instance.as_mut().get(self.id.clone())?; + let string = item.value_string()?; + Ok(string) + } + + #[wasm_bindgen] + pub unsafe fn as_data_full(&mut self) -> MizeResult { + let item = self.instance.as_mut().get(self.id.clone())?; + let data_raw = item.as_data_full()?; + let jsvalue = serde_wasm_bindgen::to_value(data_raw.cbor())?; + Ok(jsvalue) + } } + + +impl From for JsValue { + fn from(value: MizeError) -> Self { + let string = format!("{:?}", value); + string.into() + } +}