diff --git a/patch-db-macro-internals/src/lib.rs b/patch-db-macro-internals/src/lib.rs index e46c86c..7465f10 100644 --- a/patch-db-macro-internals/src/lib.rs +++ b/patch-db-macro-internals/src/lib.rs @@ -162,6 +162,11 @@ fn build_model_struct( #model_name(#inner_model::from(ptr)) } } + impl AsRef for #model_name { + fn as_ref(&self) -> &patch_db::json_ptr::JsonPointer { + self.0.as_ref() + } + } impl From> for #model_name { fn from(model: patch_db::Model<#base_name>) -> Self { #model_name(#inner_model::from(patch_db::json_ptr::JsonPointer::from(model))) @@ -237,6 +242,11 @@ fn build_model_struct( #model_name(From::from(ptr)) } } + impl AsRef for #model_name { + fn as_ref(&self) -> &patch_db::json_ptr::JsonPointer { + self.0.as_ref() + } + } impl From> for #model_name { fn from(model: patch_db::Model<#base_name>) -> Self { #model_name(model) @@ -248,11 +258,41 @@ fn build_model_struct( } } -fn build_model_enum(base: &DeriveInput, ast: &DataEnum, model_name: Option) -> TokenStream { - todo!( - "use {:?}, {:?} and {:?} to create a model that can become an enum of models", - base, - ast, - model_name - ) +fn build_model_enum(base: &DeriveInput, _: &DataEnum, model_name: Option) -> TokenStream { + let model_name = model_name.unwrap_or_else(|| { + Ident::new( + &format!("{}Model", base.ident), + proc_macro2::Span::call_site(), + ) + }); + let base_name = &base.ident; + let model_vis = &base.vis; + quote! { + #[derive(Debug, Clone)] + #model_vis struct #model_name(patch_db::Model<#base_name>); + impl core::ops::Deref for #model_name { + type Target = patch_db::Model<#base_name>; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + impl From for #model_name { + fn from(ptr: patch_db::json_ptr::JsonPointer) -> Self { + #model_name(From::from(ptr)) + } + } + impl AsRef for #model_name { + fn as_ref(&self) -> &patch_db::json_ptr::JsonPointer { + self.0.as_ref() + } + } + impl From> for #model_name { + fn from(model: patch_db::Model<#base_name>) -> Self { + #model_name(model) + } + } + impl patch_db::HasModel for #base_name { + type Model = #model_name; + } + } } diff --git a/patch-db/src/lib.rs b/patch-db/src/lib.rs index 4ca0282..3411861 100644 --- a/patch-db/src/lib.rs +++ b/patch-db/src/lib.rs @@ -15,13 +15,15 @@ mod transaction; #[cfg(test)] mod test; +pub use json_ptr; pub use locker::{LockType, Locker}; -pub use model::{BoxModel, HasModel, MapModel, Model, ModelData, ModelDataMut, VecModel}; +pub use model::{ + BoxModel, HasModel, MapModel, Model, ModelData, ModelDataMut, OptionModel, VecModel, +}; pub use patch::Revision; pub use patch_db_macro::HasModel; pub use store::{PatchDb, Store}; pub use transaction::{Checkpoint, SubTransaction, Transaction}; -pub use json_ptr; #[derive(Error, Debug)] pub enum Error { diff --git a/patch-db/src/model.rs b/patch-db/src/model.rs index a112a85..5943c2f 100644 --- a/patch-db/src/model.rs +++ b/patch-db/src/model.rs @@ -95,6 +95,14 @@ where } } } +impl AsRef for Model +where + T: Serialize + for<'de> Deserialize<'de>, +{ + fn as_ref(&self) -> &JsonPointer { + &self.ptr + } +} impl From> for JsonPointer where T: Serialize + for<'de> Deserialize<'de>, @@ -116,9 +124,10 @@ where } pub trait HasModel { - type Model: From; + type Model: From + AsRef; } +#[derive(Debug, Clone)] pub struct BoxModel Deserialize<'de>>(T::Model); impl Deserialize<'de>> Deref for BoxModel { type Target = T::Model; @@ -131,6 +140,14 @@ impl Deserialize<'de>> From>> fo BoxModel(T::Model::from(JsonPointer::from(model))) } } +impl AsRef for BoxModel +where + T: HasModel + Serialize + for<'de> Deserialize<'de>, +{ + fn as_ref(&self) -> &JsonPointer { + self.0.as_ref() + } +} impl Deserialize<'de>> From for BoxModel { fn from(ptr: JsonPointer) -> Self { BoxModel(T::Model::from(ptr)) @@ -140,6 +157,34 @@ impl Deserialize<'de>> HasModel for Box { type Model = BoxModel; } +#[derive(Debug, Clone)] +pub struct OptionModel Deserialize<'de>>(T::Model); +impl Deserialize<'de>> OptionModel { + pub async fn check(self, tx: &mut Tx) -> Result, Error> { + Ok(if tx.exists(self.0.as_ref(), None).await? { + Some(self.0) + } else { + None + }) + } +} +impl Deserialize<'de>> From>> + for OptionModel +{ + fn from(model: Model>) -> Self { + OptionModel(T::Model::from(JsonPointer::from(model))) + } +} +impl Deserialize<'de>> From for OptionModel { + fn from(ptr: JsonPointer) -> Self { + OptionModel(T::Model::from(ptr)) + } +} +impl Deserialize<'de>> HasModel for Option { + type Model = BoxModel; +} + +#[derive(Debug, Clone)] pub struct VecModel Deserialize<'de>>(Model>); impl Deserialize<'de>> Deref for VecModel { type Target = Model>; @@ -152,8 +197,8 @@ impl Deserialize<'de>> VecModel { self.child(&format!("{}", idx)) } } -impl Deserialize<'de>> From>> for VecModel { - fn from(model: Model>) -> Self { +impl Deserialize<'de>> From>> for VecModel { + fn from(model: Model>) -> Self { VecModel(From::from(JsonPointer::from(model))) } } @@ -162,10 +207,19 @@ impl Deserialize<'de>> From for VecModel VecModel(From::from(ptr)) } } +impl AsRef for VecModel +where + T: Serialize + for<'de> Deserialize<'de>, +{ + fn as_ref(&self) -> &JsonPointer { + self.0.as_ref() + } +} impl Deserialize<'de>> HasModel for Vec { type Model = VecModel; } +#[derive(Debug, Clone)] pub struct MapModel Deserialize<'de> + for<'a> Index<&'a str>>(Model); impl Deserialize<'de> + for<'a> Index<&'a str>> Deref for MapModel { type Target = Model; @@ -182,6 +236,15 @@ where self.child(idx) } } +impl MapModel +where + T: Serialize + for<'de> Deserialize<'de> + for<'a> Index<&'a str>, + for<'a, 'de> >::Output: Serialize + Deserialize<'de> + HasModel, +{ + pub fn idx_model(&self, idx: &str) -> OptionModel<>::Output> { + self.child(idx).into() + } +} impl Deserialize<'de> + for<'a> Index<&'a str>> From>> for MapModel { @@ -196,6 +259,14 @@ impl Deserialize<'de> + for<'a> Index<&'a str>> From AsRef for MapModel +where + T: Serialize + for<'de> Deserialize<'de> + for<'a> Index<&'a str>, +{ + fn as_ref(&self) -> &JsonPointer { + self.0.as_ref() + } +} impl HasModel for HashMap where K: Serialize + for<'de> Deserialize<'de> + Hash + Eq, diff --git a/patch-db/src/patch.rs b/patch-db/src/patch.rs index 7f488aa..b9d46a0 100644 --- a/patch-db/src/patch.rs +++ b/patch-db/src/patch.rs @@ -77,6 +77,7 @@ impl DiffPatch { .collect(), )) } + pub fn rebase(&mut self, onto: &DiffPatch) { let DiffPatch(Patch(ops)) = self; let DiffPatch(Patch(onto_ops)) = onto; @@ -148,6 +149,31 @@ impl DiffPatch { } } } + + pub fn exists(&self) -> Option { + let mut res = None; + for op in &(self.0).0 { + match op { + PatchOperation::Add(a) => { + if a.path.is_empty() { + res = Some(!a.value.is_null()); + } + } + PatchOperation::Replace(a) => { + if a.path.is_empty() { + res = Some(!a.value.is_null()) + } + } + PatchOperation::Remove(a) => { + if a.path.is_empty() { + res = Some(false) + } + } + _ => unreachable!(), + } + } + res + } } impl Default for DiffPatch { fn default() -> Self { diff --git a/patch-db/src/store.rs b/patch-db/src/store.rs index cb6199e..9c0ae34 100644 --- a/patch-db/src/store.rs +++ b/patch-db/src/store.rs @@ -123,6 +123,12 @@ impl Store { self.file.unlock(true).map_err(|e| e.1)?; Ok(()) } + pub fn exists, V: SegList>( + &self, + ptr: &JsonPointer, + ) -> Result { + Ok(ptr.get(self.get_data()?).unwrap_or(&Value::Null) != &Value::Null) + } pub fn get Deserialize<'de>, S: AsRef, V: SegList>( &self, ptr: &JsonPointer, @@ -190,6 +196,12 @@ impl PatchDb { subscriber: Arc::new(subscriber), }) } + pub async fn exists, V: SegList>( + &self, + ptr: &JsonPointer, + ) -> Result { + self.store.read().await.exists(ptr) + } pub async fn get Deserialize<'de>, S: AsRef, V: SegList>( &self, ptr: &JsonPointer, diff --git a/patch-db/src/transaction.rs b/patch-db/src/transaction.rs index fef9f0b..3c20aeb 100644 --- a/patch-db/src/transaction.rs +++ b/patch-db/src/transaction.rs @@ -15,6 +15,11 @@ use crate::Error; pub trait Checkpoint: Sized { fn rebase(&mut self) -> Result<(), Error>; + fn exists<'a, S: AsRef + Send + Sync + 'a, V: SegList + Send + Sync + 'a>( + &'a mut self, + ptr: &'a JsonPointer, + store_read_lock: Option>, + ) -> BoxFuture<'a, Result>; fn get_value<'a, S: AsRef + Send + Sync + 'a, V: SegList + Send + Sync + 'a>( &'a mut self, ptr: &'a JsonPointer, @@ -73,6 +78,23 @@ impl Transaction { } Ok(()) } + async fn exists, V: SegList>( + &mut self, + ptr: &JsonPointer, + store_read_lock: Option>, + ) -> Result { + let exists = { + let store_lock = self.db.store.clone(); + let store = if let Some(store_read_lock) = store_read_lock { + store_read_lock + } else { + store_lock.read().await + }; + self.rebase()?; + ptr.get(store.get_data()?).unwrap_or(&Value::Null) != &Value::Null + }; + Ok(self.updates.for_path(ptr).exists().unwrap_or(exists)) + } async fn get_value, V: SegList>( &mut self, ptr: &JsonPointer, @@ -164,6 +186,13 @@ impl<'a> Checkpoint for &'a mut Transaction { fn rebase(&mut self) -> Result<(), Error> { Transaction::rebase(self) } + fn exists<'b, S: AsRef + Send + Sync + 'b, V: SegList + Send + Sync + 'b>( + &'b mut self, + ptr: &'b JsonPointer, + store_read_lock: Option>, + ) -> BoxFuture<'b, Result> { + Transaction::exists(self, ptr, store_read_lock).boxed() + } fn get_value<'b, S: AsRef + Send + Sync + 'b, V: SegList + Send + Sync + 'b>( &'b mut self, ptr: &'b JsonPointer, @@ -241,6 +270,23 @@ impl SubTransaction { } Ok(()) } + async fn exists + Send + Sync, V: SegList + Send + Sync>( + &mut self, + ptr: &JsonPointer, + store_read_lock: Option>, + ) -> Result { + let exists = { + let store_lock = self.parent.store(); + let store = if let Some(store_read_lock) = store_read_lock { + store_read_lock + } else { + store_lock.read().await + }; + self.rebase()?; + self.parent.exists(ptr, Some(store)).await? + }; + Ok(self.updates.for_path(ptr).exists().unwrap_or(exists)) + } async fn get_value + Send + Sync, V: SegList + Send + Sync>( &mut self, ptr: &JsonPointer, @@ -336,6 +382,13 @@ impl<'a, Tx: Checkpoint + Send + Sync> Checkpoint for &'a mut SubTransaction fn rebase(&mut self) -> Result<(), Error> { SubTransaction::rebase(self) } + fn exists<'b, S: AsRef + Send + Sync + 'b, V: SegList + Send + Sync + 'b>( + &'b mut self, + ptr: &'b JsonPointer, + store_read_lock: Option>, + ) -> BoxFuture<'b, Result> { + SubTransaction::exists(self, ptr, store_read_lock).boxed() + } fn get_value<'b, S: AsRef + Send + Sync + 'b, V: SegList + Send + Sync + 'b>( &'b mut self, ptr: &'b JsonPointer,