diff --git a/patch-db-macro-internals/src/lib.rs b/patch-db-macro-internals/src/lib.rs index fef8cf4..a4b1310 100644 --- a/patch-db-macro-internals/src/lib.rs +++ b/patch-db-macro-internals/src/lib.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{ - Data, DataEnum, DataStruct, DeriveInput, Fields, Ident, Lit, LitStr, MetaNameValue, Type, + Data, DataEnum, DataStruct, DeriveInput, Fields, Ident, Lit, LitStr, MetaNameValue, Path, Type, }; pub fn build_model(item: &DeriveInput) -> TokenStream { @@ -43,7 +43,7 @@ fn build_model_struct( let model_vis = &base.vis; let mut child_fn_name: Vec = Vec::new(); let mut child_model: Vec = Vec::new(); - let mut child_path: Vec = Vec::new(); + let mut child_path: Vec> = Vec::new(); let serde_rename_all = base .attrs .iter() @@ -81,47 +81,66 @@ fn build_model_struct( } else { child_model.push(syn::parse2(quote! { patch_db::Model<#ty> }).unwrap()); } - let serde_rename = field + if field .attrs .iter() .filter(|attr| attr.path.is_ident("serde")) - .filter_map(|attr| syn::parse2::(attr.tokens.clone()).ok()) - .filter(|nv| nv.path.is_ident("rename")) - .find_map(|nv| match nv.lit { - Lit::Str(s) => Some(s), - _ => None, - }); - match (serde_rename, serde_rename_all.as_ref().map(|s| s.as_str())) { - (Some(a), _) => child_path.push(a), - (None, Some("lowercase")) => child_path.push(LitStr::new( - &heck::CamelCase::to_camel_case(ident.to_string().as_str()).to_lowercase(), - ident.span(), - )), - (None, Some("UPPERCASE")) => child_path.push(LitStr::new( - &heck::CamelCase::to_camel_case(ident.to_string().as_str()).to_uppercase(), - ident.span(), - )), - (None, Some("PascalCase")) => child_path.push(LitStr::new( - &heck::CamelCase::to_camel_case(ident.to_string().as_str()), - ident.span(), - )), - (None, Some("camelCase")) => child_path.push(LitStr::new( - &heck::MixedCase::to_mixed_case(ident.to_string().as_str()), - ident.span(), - )), - (None, Some("SCREAMING_SNAKE_CASE")) => child_path.push(LitStr::new( - &heck::ShoutySnakeCase::to_shouty_snake_case(ident.to_string().as_str()), - ident.span(), - )), - (None, Some("kebab-case")) => child_path.push(LitStr::new( - &heck::KebabCase::to_kebab_case(ident.to_string().as_str()), - ident.span(), - )), - (None, Some("SCREAMING-KEBAB-CASE")) => child_path.push(LitStr::new( - &heck::ShoutyKebabCase::to_shouty_kebab_case(ident.to_string().as_str()), - ident.span(), - )), - _ => child_path.push(LitStr::new(&ident.to_string(), ident.span())), + .filter_map(|attr| syn::parse2::(attr.tokens.clone()).ok()) + .any(|path| path.is_ident("flatten")) + { + child_path.push(None); + } else { + let serde_rename = field + .attrs + .iter() + .filter(|attr| attr.path.is_ident("serde")) + .filter_map(|attr| syn::parse2::(attr.tokens.clone()).ok()) + .filter(|nv| nv.path.is_ident("rename")) + .find_map(|nv| match nv.lit { + Lit::Str(s) => Some(s), + _ => None, + }); + + child_path.push(Some( + match (serde_rename, serde_rename_all.as_ref().map(|s| s.as_str())) { + (Some(a), _) => a, + (None, Some("lowercase")) => LitStr::new( + &heck::CamelCase::to_camel_case(ident.to_string().as_str()) + .to_lowercase(), + ident.span(), + ), + (None, Some("UPPERCASE")) => LitStr::new( + &heck::CamelCase::to_camel_case(ident.to_string().as_str()) + .to_uppercase(), + ident.span(), + ), + (None, Some("PascalCase")) => LitStr::new( + &heck::CamelCase::to_camel_case(ident.to_string().as_str()), + ident.span(), + ), + (None, Some("camelCase")) => LitStr::new( + &heck::MixedCase::to_mixed_case(ident.to_string().as_str()), + ident.span(), + ), + (None, Some("SCREAMING_SNAKE_CASE")) => LitStr::new( + &heck::ShoutySnakeCase::to_shouty_snake_case( + ident.to_string().as_str(), + ), + ident.span(), + ), + (None, Some("kebab-case")) => LitStr::new( + &heck::KebabCase::to_kebab_case(ident.to_string().as_str()), + ident.span(), + ), + (None, Some("SCREAMING-KEBAB-CASE")) => LitStr::new( + &heck::ShoutyKebabCase::to_shouty_kebab_case( + ident.to_string().as_str(), + ), + ident.span(), + ), + _ => LitStr::new(&ident.to_string(), ident.span()), + }, + )); } } } @@ -222,15 +241,27 @@ fn build_model_struct( child_model.push(syn::parse2(quote! { patch_db::Model<#ty> }).unwrap()); } // TODO: serde rename for tuple structs? - child_path.push(LitStr::new( + // TODO: serde flatten for tuple structs? + child_path.push(Some(LitStr::new( &format!("{}", i), proc_macro2::Span::call_site(), - )); + ))); } } } Fields::Unit => (), } + let child_path_expr = child_path.iter().map(|child_path| { + if let Some(child_path) = child_path { + quote! { + self.0.child(#child_path).into() + } + } else { + quote! { + self.0.into() + } + } + }); quote! { #[derive(Debug)] #model_vis struct #model_name(patch_db::Model<#base_name>); @@ -248,7 +279,7 @@ fn build_model_struct( impl #model_name { #( pub fn #child_fn_name(self) -> #child_model { - self.0.child(#child_path).into() + #child_path_expr } )* } diff --git a/patch-db/src/lib.rs b/patch-db/src/lib.rs index 3c61e4f..0433447 100644 --- a/patch-db/src/lib.rs +++ b/patch-db/src/lib.rs @@ -23,7 +23,7 @@ pub use locker::{LockType, Locker}; pub use model::{ BoxModel, HasModel, Map, MapModel, Model, ModelData, ModelDataMut, OptionModel, VecModel, }; -pub use patch::Revision; +pub use patch::{Revision, DiffPatch}; pub use patch_db_macro::HasModel; pub use store::{PatchDb, Store}; pub use transaction::Transaction; diff --git a/patch-db/src/locker.rs b/patch-db/src/locker.rs index 27e53f6..6d26f70 100644 --- a/patch-db/src/locker.rs +++ b/patch-db/src/locker.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::sync::Arc; +use futures::{future::BoxFuture, FutureExt}; use json_ptr::{JsonPointer, SegList}; use qutex_2::{QrwLock, ReadGuard, WriteGuard}; use tokio::sync::Mutex; @@ -67,6 +68,15 @@ impl Locker { pub fn new() -> Self { Locker(QrwLock::new(HashMap::new())) } + fn lock_root_read<'a>(guard: &'a ReadGuard>) -> BoxFuture<'a, ()> { + async move { + for (_, v) in &**guard { + let g = v.0.clone().read().await.unwrap(); + Self::lock_root_read(&g).await; + } + } + .boxed() + } pub async fn lock_read, V: SegList>( &self, ptr: &JsonPointer, @@ -83,7 +93,9 @@ impl Locker { }; lock = Some(new_lock); } - lock.unwrap() + let res = lock.unwrap(); + Self::lock_root_read(&res); + res } pub(crate) async fn add_read_lock + Clone, V: SegList + Clone>( &self, @@ -105,6 +117,15 @@ impl Locker { LockerGuard::Read(self.lock_read(ptr).await.into()), )); } + fn lock_root_write<'a>(guard: &'a WriteGuard>) -> BoxFuture<'a, ()> { + async move { + for (_, v) in &**guard { + let g = v.0.clone().write().await.unwrap(); + Self::lock_root_write(&g).await; + } + } + .boxed() + } pub async fn lock_write, V: SegList>( &self, ptr: &JsonPointer, @@ -119,7 +140,9 @@ impl Locker { }; lock = new_lock; } - lock + let res = lock; + Self::lock_root_write(&res); + res } pub(crate) async fn add_write_lock + Clone, V: SegList + Clone>( &self, diff --git a/patch-db/src/model.rs b/patch-db/src/model.rs index 01467c9..b0f2301 100644 --- a/patch-db/src/model.rs +++ b/patch-db/src/model.rs @@ -11,6 +11,7 @@ use serde_json::Value; use crate::Error; use crate::{locker::LockType, DbHandle}; +#[derive(Debug)] pub struct ModelData Deserialize<'de>>(T); impl Deserialize<'de>> Deref for ModelData { type Target = T; @@ -18,7 +19,13 @@ impl Deserialize<'de>> Deref for ModelData { &self.0 } } +impl Deserialize<'de>> ModelData { + pub fn to_owned(self) -> T { + self.0 + } +} +#[derive(Debug)] pub struct ModelDataMut Deserialize<'de>> { original: Value, current: T, diff --git a/patch-db/src/transaction.rs b/patch-db/src/transaction.rs index b68e2d8..3f146e5 100644 --- a/patch-db/src/transaction.rs +++ b/patch-db/src/transaction.rs @@ -35,6 +35,12 @@ impl Transaction<&mut PatchDbHandle> { drop(store); Ok(rev) } + pub async fn abort(mut self) -> Result { + let store_lock = self.parent.store(); + let _store = store_lock.read().await; + self.rebase()?; + Ok(self.updates) + } } impl Transaction { pub async fn save(mut self) -> Result<(), Error> {