mirror of
https://github.com/Start9Labs/patch-db.git
synced 2026-03-26 10:21:53 +00:00
improved api
This commit is contained in:
@@ -8,8 +8,9 @@ use syn::punctuated::Punctuated;
|
|||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::token::{Comma, Paren, Pub};
|
use syn::token::{Comma, Paren, Pub};
|
||||||
use syn::{
|
use syn::{
|
||||||
Attribute, Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Ident, Lit, LitInt, LitStr,
|
Attribute, Data, DataEnum, DataStruct, DeriveInput, Error, Fields, GenericArgument, Ident, Lit,
|
||||||
MetaNameValue, Path, Type, VisRestricted, Visibility,
|
LitInt, LitStr, Meta, MetaNameValue, Path, PathArguments, Type, TypePath, VisRestricted,
|
||||||
|
Visibility,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn build_model(item: &DeriveInput) -> TokenStream {
|
pub fn build_model(item: &DeriveInput) -> TokenStream {
|
||||||
@@ -126,12 +127,17 @@ impl ChildInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Fns {
|
struct Fns {
|
||||||
|
from_parts: TokenStream,
|
||||||
impl_fns: TokenStream,
|
impl_fns: TokenStream,
|
||||||
|
impl_ref_fns: TokenStream,
|
||||||
impl_mut_fns: TokenStream,
|
impl_mut_fns: TokenStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn impl_fns(children: &[ChildInfo]) -> Fns {
|
fn impl_fns(model_ty: &Type, children: &[ChildInfo]) -> Fns {
|
||||||
|
let mut parts_args = TokenStream::new();
|
||||||
|
let mut parts_assignments = TokenStream::new();
|
||||||
let mut impl_fns = TokenStream::new();
|
let mut impl_fns = TokenStream::new();
|
||||||
|
let mut impl_ref_fns = TokenStream::new();
|
||||||
let mut impl_mut_fns = TokenStream::new();
|
let mut impl_mut_fns = TokenStream::new();
|
||||||
for ChildInfo {
|
for ChildInfo {
|
||||||
vis,
|
vis,
|
||||||
@@ -141,6 +147,7 @@ fn impl_fns(children: &[ChildInfo]) -> Fns {
|
|||||||
} in children
|
} in children
|
||||||
{
|
{
|
||||||
let name_owned = Ident::new(&format!("into_{name}"), name.span());
|
let name_owned = Ident::new(&format!("into_{name}"), name.span());
|
||||||
|
let name_ref = Ident::new(&format!("as_{name}"), name.span());
|
||||||
let name_mut = Ident::new(&format!("as_{name}_mut"), name.span());
|
let name_mut = Ident::new(&format!("as_{name}_mut"), name.span());
|
||||||
let vis = match vis {
|
let vis = match vis {
|
||||||
Visibility::Inherited => Visibility::Restricted(VisRestricted {
|
Visibility::Inherited => Visibility::Restricted(VisRestricted {
|
||||||
@@ -181,6 +188,17 @@ fn impl_fns(children: &[ChildInfo]) -> Fns {
|
|||||||
} else {
|
} else {
|
||||||
quote! { v }
|
quote! { v }
|
||||||
};
|
};
|
||||||
|
let accessor_ref = if let Some(accessor) = accessor {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use patch_db::value::index::Index;
|
||||||
|
#accessor.index_into(v).unwrap_or(&patch_db::value::NULL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! { v }
|
||||||
|
};
|
||||||
let accessor_mut = if let Some(accessor) = accessor {
|
let accessor_mut = if let Some(accessor) = accessor {
|
||||||
quote! {
|
quote! {
|
||||||
{
|
{
|
||||||
@@ -192,24 +210,128 @@ fn impl_fns(children: &[ChildInfo]) -> Fns {
|
|||||||
} else {
|
} else {
|
||||||
quote! { v }
|
quote! { v }
|
||||||
};
|
};
|
||||||
|
let child_model_ty = replace_self(model_ty.clone(), &ty);
|
||||||
|
parts_args.extend(quote_spanned! { name.span() =>
|
||||||
|
#name: #child_model_ty,
|
||||||
|
});
|
||||||
|
parts_assignments.extend(quote_spanned! { name.span() =>
|
||||||
|
*#accessor_mut = #name.into_value();
|
||||||
|
});
|
||||||
impl_fns.extend(quote_spanned! { name.span() =>
|
impl_fns.extend(quote_spanned! { name.span() =>
|
||||||
#vis fn #name_owned (self) -> patch_db::Model::<#ty> {
|
#vis fn #name_owned (self) -> #child_model_ty {
|
||||||
|
use patch_db::ModelExt;
|
||||||
self.transmute(|v| #accessor_owned)
|
self.transmute(|v| #accessor_owned)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
impl_ref_fns.extend(quote_spanned! { name.span() =>
|
||||||
|
#vis fn #name_ref (&self) -> &#child_model_ty {
|
||||||
|
use patch_db::ModelExt;
|
||||||
|
self.transmute_ref(|v| #accessor_ref)
|
||||||
|
}
|
||||||
|
});
|
||||||
impl_mut_fns.extend(quote_spanned! { name.span() =>
|
impl_mut_fns.extend(quote_spanned! { name.span() =>
|
||||||
#vis fn #name_mut (&mut self) -> &mut patch_db::Model::<#ty> {
|
#vis fn #name_mut (&mut self) -> &mut #child_model_ty {
|
||||||
|
use patch_db::ModelExt;
|
||||||
self.transmute_mut(|v| #accessor_mut)
|
self.transmute_mut(|v| #accessor_mut)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Fns {
|
Fns {
|
||||||
|
from_parts: quote! {
|
||||||
|
pub fn from_parts(#parts_args) -> Self {
|
||||||
|
use patch_db::ModelExt;
|
||||||
|
let mut res = patch_db::value::json!({});
|
||||||
|
let v = &mut res;
|
||||||
|
#parts_assignments
|
||||||
|
Self::from_value(res)
|
||||||
|
}
|
||||||
|
},
|
||||||
impl_fns,
|
impl_fns,
|
||||||
|
impl_ref_fns,
|
||||||
impl_mut_fns,
|
impl_mut_fns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn replace_self(ty: Type, replace: &Type) -> Type {
|
||||||
|
match ty {
|
||||||
|
Type::Path(mut a) => Type::Path({
|
||||||
|
a.path.segments = a
|
||||||
|
.path
|
||||||
|
.segments
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut s| {
|
||||||
|
s.arguments = match s.arguments {
|
||||||
|
PathArguments::AngleBracketed(mut a) => PathArguments::AngleBracketed({
|
||||||
|
a.args = a
|
||||||
|
.args
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| match a {
|
||||||
|
GenericArgument::Type(Type::Path(a)) => {
|
||||||
|
GenericArgument::Type({
|
||||||
|
if a.path.is_ident("Self") {
|
||||||
|
replace.clone()
|
||||||
|
} else {
|
||||||
|
Type::Path(a)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
a => a,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
a
|
||||||
|
}),
|
||||||
|
a => a,
|
||||||
|
};
|
||||||
|
s
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
a
|
||||||
|
}),
|
||||||
|
a => a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn build_model_struct(base: &DeriveInput, ast: &DataStruct) -> TokenStream {
|
fn build_model_struct(base: &DeriveInput, ast: &DataStruct) -> TokenStream {
|
||||||
|
let model_ty = match base
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.filter(|attr| attr.path.is_ident("model"))
|
||||||
|
.filter_map(|attr| attr.parse_meta().ok())
|
||||||
|
.find_map(|meta| {
|
||||||
|
if let Meta::NameValue(a) = meta {
|
||||||
|
Some(a)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::new(
|
||||||
|
base.ident.span(),
|
||||||
|
"could not determine model type\n#[model = \"...\"] is required",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and_then(|meta| {
|
||||||
|
if let Lit::Str(a) = meta.lit {
|
||||||
|
Ok(a)
|
||||||
|
} else {
|
||||||
|
Err(Error::new(
|
||||||
|
meta.lit.span(),
|
||||||
|
"syntax error: expected string literal",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.and_then(|s| syn::parse_str::<Type>(&s.value()))
|
||||||
|
{
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => return e.into_compile_error(),
|
||||||
|
};
|
||||||
|
let model_ty_name = replace_self(
|
||||||
|
model_ty.clone(),
|
||||||
|
&Type::Path(TypePath {
|
||||||
|
qself: None,
|
||||||
|
path: base.ident.clone().into(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
let serde_rename_all = base
|
let serde_rename_all = base
|
||||||
.attrs
|
.attrs
|
||||||
.iter()
|
.iter()
|
||||||
@@ -223,26 +345,73 @@ fn build_model_struct(base: &DeriveInput, ast: &DataStruct) -> TokenStream {
|
|||||||
let children = ChildInfo::from_fields(&serde_rename_all, &ast.fields);
|
let children = ChildInfo::from_fields(&serde_rename_all, &ast.fields);
|
||||||
let name = &base.ident;
|
let name = &base.ident;
|
||||||
let Fns {
|
let Fns {
|
||||||
|
from_parts,
|
||||||
impl_fns,
|
impl_fns,
|
||||||
|
impl_ref_fns,
|
||||||
impl_mut_fns,
|
impl_mut_fns,
|
||||||
} = impl_fns(&children);
|
} = impl_fns(&model_ty, &children);
|
||||||
quote! {
|
quote! {
|
||||||
impl patch_db::HasModel for #name {}
|
impl patch_db::HasModel for #name {
|
||||||
impl patch_db::Model<#name> {
|
type Model = #model_ty;
|
||||||
|
}
|
||||||
|
impl #model_ty_name {
|
||||||
|
#from_parts
|
||||||
#impl_fns
|
#impl_fns
|
||||||
|
#impl_ref_fns
|
||||||
#impl_mut_fns
|
#impl_mut_fns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
||||||
|
let model_ty = match base
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.filter(|attr| attr.path.is_ident("model"))
|
||||||
|
.filter_map(|attr| attr.parse_meta().ok())
|
||||||
|
.find_map(|meta| {
|
||||||
|
if let Meta::NameValue(a) = meta {
|
||||||
|
Some(a)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::new(
|
||||||
|
base.ident.span(),
|
||||||
|
"could not determine model type\n#[model = \"...\"] is required",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and_then(|meta| {
|
||||||
|
if let Lit::Str(a) = meta.lit {
|
||||||
|
Ok(a)
|
||||||
|
} else {
|
||||||
|
Err(Error::new(
|
||||||
|
meta.lit.span(),
|
||||||
|
"syntax error: expected string literal",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.and_then(|s| syn::parse_str::<Type>(&s.value()))
|
||||||
|
{
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => return e.into_compile_error(),
|
||||||
|
};
|
||||||
|
let model_ty_name = replace_self(
|
||||||
|
model_ty.clone(),
|
||||||
|
&Type::Path(TypePath {
|
||||||
|
qself: None,
|
||||||
|
path: base.ident.clone().into(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
let mut match_name = None;
|
let mut match_name = None;
|
||||||
|
let mut match_name_ref = None;
|
||||||
let mut match_name_mut = None;
|
let mut match_name_mut = None;
|
||||||
for arg in base
|
for arg in base
|
||||||
.attrs
|
.attrs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|attr| attr.path.is_ident("model"))
|
.filter(|attr| attr.path.is_ident("model"))
|
||||||
.map(|attr| attr.parse_args::<MetaNameValue>().unwrap())
|
.filter_map(|attr| attr.parse_args::<MetaNameValue>().ok())
|
||||||
{
|
{
|
||||||
match arg {
|
match arg {
|
||||||
MetaNameValue {
|
MetaNameValue {
|
||||||
@@ -250,6 +419,11 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
|||||||
lit: Lit::Str(s),
|
lit: Lit::Str(s),
|
||||||
..
|
..
|
||||||
} if path.is_ident("match") => match_name = Some(s.parse().unwrap()),
|
} if path.is_ident("match") => match_name = Some(s.parse().unwrap()),
|
||||||
|
MetaNameValue {
|
||||||
|
path,
|
||||||
|
lit: Lit::Str(s),
|
||||||
|
..
|
||||||
|
} if path.is_ident("match_ref") => match_name_ref = Some(s.parse().unwrap()),
|
||||||
MetaNameValue {
|
MetaNameValue {
|
||||||
path,
|
path,
|
||||||
lit: Lit::Str(s),
|
lit: Lit::Str(s),
|
||||||
@@ -260,6 +434,8 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
|||||||
}
|
}
|
||||||
let match_name = match_name
|
let match_name = match_name
|
||||||
.unwrap_or_else(|| Ident::new(&format!("{}MatchModel", base.ident), Span::call_site()));
|
.unwrap_or_else(|| Ident::new(&format!("{}MatchModel", base.ident), Span::call_site()));
|
||||||
|
let match_name_ref = match_name_ref
|
||||||
|
.unwrap_or_else(|| Ident::new(&format!("{}MatchModelRef", base.ident), Span::call_site()));
|
||||||
let match_name_mut = match_name_mut
|
let match_name_mut = match_name_mut
|
||||||
.unwrap_or_else(|| Ident::new(&format!("{}MatchModelMut", base.ident), Span::call_site()));
|
.unwrap_or_else(|| Ident::new(&format!("{}MatchModelMut", base.ident), Span::call_site()));
|
||||||
let serde_rename_all = base
|
let serde_rename_all = base
|
||||||
@@ -304,10 +480,15 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let mut model_variants = TokenStream::new();
|
let mut model_variants = TokenStream::new();
|
||||||
|
let mut ref_model_variants = TokenStream::new();
|
||||||
let mut mut_model_variants = TokenStream::new();
|
let mut mut_model_variants = TokenStream::new();
|
||||||
let (impl_new, impl_new_mut) = if let Some(Lit::Str(tag)) = serde_tag.remove("tag") {
|
let (impl_new, impl_unmatch, impl_new_ref, impl_new_mut) = if let Some(Lit::Str(tag)) =
|
||||||
|
serde_tag.remove("tag")
|
||||||
|
{
|
||||||
if let Some(Lit::Str(content)) = serde_tag.remove("content") {
|
if let Some(Lit::Str(content)) = serde_tag.remove("content") {
|
||||||
let mut tag_variants = TokenStream::new();
|
let mut tag_variants = TokenStream::new();
|
||||||
|
let mut tag_variants_unmatch = TokenStream::new();
|
||||||
|
let mut tag_variants_ref = TokenStream::new();
|
||||||
let mut tag_variants_mut = TokenStream::new();
|
let mut tag_variants_mut = TokenStream::new();
|
||||||
for variant in &ast.variants {
|
for variant in &ast.variants {
|
||||||
let variant_name = &variant.ident;
|
let variant_name = &variant.ident;
|
||||||
@@ -320,8 +501,22 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
|||||||
#content.index_into_owned(v).unwrap_or_default()
|
#content.index_into_owned(v).unwrap_or_default()
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
tag_variants_unmatch.extend(quote_spanned! { variant_name.span() =>
|
||||||
|
#match_name::#variant_name(v) => Self::from_value({
|
||||||
|
let mut a = patch_db::value::json!({ #tag: #variant_accessor });
|
||||||
|
a[#content] = v.into_value();
|
||||||
|
a
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
tag_variants_ref.extend(quote_spanned! { variant_name.span() =>
|
||||||
|
Some(#variant_accessor) => #match_name_ref::#variant_name(self.transmute_ref(|v| {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use patch_db::value::index::Index;
|
||||||
|
#content.index_into(v).unwrap_or(&patch_db::value::NULL)
|
||||||
|
})),
|
||||||
|
});
|
||||||
tag_variants_mut.extend(quote_spanned! { variant_name.span() =>
|
tag_variants_mut.extend(quote_spanned! { variant_name.span() =>
|
||||||
Some(#variant_accessor) => #match_name_mut::#variant_name(self.transmute(|v| {
|
Some(#variant_accessor) => #match_name_mut::#variant_name(self.transmute_mut(|v| {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use patch_db::value::index::Index;
|
use patch_db::value::index::Index;
|
||||||
#content.index_or_insert(v)
|
#content.index_or_insert(v)
|
||||||
@@ -330,13 +525,29 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
|||||||
}
|
}
|
||||||
(
|
(
|
||||||
quote! {
|
quote! {
|
||||||
match self[#tag].as_str() {
|
use patch_db::ModelExt;
|
||||||
|
match self.as_value()[#tag].as_str() {
|
||||||
#tag_variants
|
#tag_variants
|
||||||
_ => #match_name::Error(self.into_inner()),
|
_ => #match_name::Error(self.into_inner()),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
quote! {
|
quote! {
|
||||||
match self[#tag].as_str() {
|
use patch_db::ModelExt;
|
||||||
|
match value {
|
||||||
|
#tag_variants_unmatch
|
||||||
|
#match_name::Error(v) => Self::from_value(v),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quote! {
|
||||||
|
use patch_db::ModelExt;
|
||||||
|
match self.as_value()[#tag].as_str() {
|
||||||
|
#tag_variants_ref
|
||||||
|
_ => #match_name_ref::Error(&*self),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quote! {
|
||||||
|
use patch_db::ModelExt;
|
||||||
|
match self.as_value()[#tag].as_str() {
|
||||||
#tag_variants_mut
|
#tag_variants_mut
|
||||||
_ => #match_name_mut::Error(&mut *self),
|
_ => #match_name_mut::Error(&mut *self),
|
||||||
}
|
}
|
||||||
@@ -344,6 +555,8 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let mut tag_variants = TokenStream::new();
|
let mut tag_variants = TokenStream::new();
|
||||||
|
let mut tag_variants_unmatch = TokenStream::new();
|
||||||
|
let mut tag_variants_ref = TokenStream::new();
|
||||||
let mut tag_variants_mut = TokenStream::new();
|
let mut tag_variants_mut = TokenStream::new();
|
||||||
for variant in &ast.variants {
|
for variant in &ast.variants {
|
||||||
let variant_name = &variant.ident;
|
let variant_name = &variant.ident;
|
||||||
@@ -356,8 +569,22 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
|||||||
#variant_accessor.index_into_owned(v).unwrap_or_default()
|
#variant_accessor.index_into_owned(v).unwrap_or_default()
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
tag_variants_unmatch.extend(quote_spanned! { variant_name.span() =>
|
||||||
|
#match_name::#variant_name(v) => Self::from_value({
|
||||||
|
let mut a = v.into_value();
|
||||||
|
a[#tag] = patch_db::Value::String(std::sync::Arc::new(#variant_accessor.to_owned()));
|
||||||
|
a
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
tag_variants_ref.extend(quote_spanned! { variant_name.span() =>
|
||||||
|
Some(#variant_accessor) => #match_name_ref::#variant_name(self.transmute_ref(|v| {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use patch_db::value::index::Index;
|
||||||
|
#variant_accessor.index_into(v).unwrap_or(&patch_db::value::NULL)
|
||||||
|
})),
|
||||||
|
});
|
||||||
tag_variants_mut.extend(quote_spanned! { variant_name.span() =>
|
tag_variants_mut.extend(quote_spanned! { variant_name.span() =>
|
||||||
Some(#variant_accessor) => #match_name_mut::#variant_name(self.transmute(|v| {
|
Some(#variant_accessor) => #match_name_mut::#variant_name(self.transmute_mut(|v| {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use patch_db::value::index::Index;
|
use patch_db::value::index::Index;
|
||||||
#variant_accessor.index_or_insert(v)
|
#variant_accessor.index_or_insert(v)
|
||||||
@@ -366,21 +593,39 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
|||||||
}
|
}
|
||||||
(
|
(
|
||||||
quote! {
|
quote! {
|
||||||
match self[#tag].as_str() {
|
use patch_db::ModelExt;
|
||||||
|
match self.as_value()[#tag].as_str() {
|
||||||
#tag_variants
|
#tag_variants
|
||||||
_ => #match_name::Error(self.into_inner()),
|
_ => #match_name::Error(self.into_value()),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
quote! {
|
quote! {
|
||||||
match self[#tag].as_str() {
|
use patch_db::ModelExt;
|
||||||
|
match value {
|
||||||
|
#tag_variants_unmatch
|
||||||
|
#match_name::Error(v) => Self::from_value(v),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quote! {
|
||||||
|
use patch_db::ModelExt;
|
||||||
|
match self.as_value()[#tag].as_str() {
|
||||||
|
#tag_variants_ref
|
||||||
|
_ => #match_name_ref::Error(self.as_value()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quote! {
|
||||||
|
use patch_db::ModelExt;
|
||||||
|
match self.as_value()[#tag].as_str() {
|
||||||
#tag_variants_mut
|
#tag_variants_mut
|
||||||
_ => #match_name_mut::Error(&mut *self),
|
_ => #match_name_mut::Error(self.as_value_mut()),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut tag_variants = TokenStream::new();
|
let mut tag_variants = TokenStream::new();
|
||||||
|
let mut tag_variants_unmatch = TokenStream::new();
|
||||||
|
let mut tag_variants_ref = TokenStream::new();
|
||||||
let mut tag_variants_mut = TokenStream::new();
|
let mut tag_variants_mut = TokenStream::new();
|
||||||
for variant in &ast.variants {
|
for variant in &ast.variants {
|
||||||
let variant_name = &variant.ident;
|
let variant_name = &variant.ident;
|
||||||
@@ -394,9 +639,25 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
|||||||
}))
|
}))
|
||||||
} else
|
} else
|
||||||
});
|
});
|
||||||
|
tag_variants_unmatch.extend(quote_spanned! { variant_name.span() =>
|
||||||
|
#match_name::#variant_name(v) => Self::from_value({
|
||||||
|
let mut a = patch_db::value::json!({});
|
||||||
|
a[#variant_accessor] = v.into_value();
|
||||||
|
a
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
tag_variants_ref.extend(quote_spanned! { variant_name.span() =>
|
||||||
|
if value.as_object().map(|o| o.contains_key(#variant_accessor)).unwrap_or(false) {
|
||||||
|
#match_name_ref::#variant_name(self.transmute_ref(|v| {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use patch_db::value::index::Index;
|
||||||
|
#variant_accessor.index_into(v).unwrap_or(&patch_db::value::NULL)
|
||||||
|
}))
|
||||||
|
} else
|
||||||
|
});
|
||||||
tag_variants_mut.extend(quote_spanned! { variant_name.span() =>
|
tag_variants_mut.extend(quote_spanned! { variant_name.span() =>
|
||||||
if value.as_object().map(|o| o.contains_key(#variant_accessor)).unwrap_or(false) {
|
if value.as_object().map(|o| o.contains_key(#variant_accessor)).unwrap_or(false) {
|
||||||
#match_name_mut::#variant_name(self.transmute(|v| {
|
#match_name_mut::#variant_name(self.transmute_mut(|v| {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use patch_db::value::index::Index;
|
use patch_db::value::index::Index;
|
||||||
#variant_accessor.index_or_insert(v)
|
#variant_accessor.index_or_insert(v)
|
||||||
@@ -406,11 +667,26 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
|||||||
}
|
}
|
||||||
(
|
(
|
||||||
quote! {
|
quote! {
|
||||||
|
use patch_db::ModelExt;
|
||||||
#tag_variants {
|
#tag_variants {
|
||||||
#match_name::Error(self.into_inner()),
|
#match_name::Error(self.into_inner()),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
quote! {
|
quote! {
|
||||||
|
use patch_db::ModelExt;
|
||||||
|
match value {
|
||||||
|
#tag_variants_unmatch
|
||||||
|
#match_name::Error(v) => Self::from_value(v),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quote! {
|
||||||
|
use patch_db::ModelExt;
|
||||||
|
#tag_variants_ref {
|
||||||
|
#match_name_ref::Error(&*self),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quote! {
|
||||||
|
use patch_db::ModelExt;
|
||||||
#tag_variants_mut {
|
#tag_variants_mut {
|
||||||
#match_name_mut::Error(&mut *self),
|
#match_name_mut::Error(&mut *self),
|
||||||
}
|
}
|
||||||
@@ -429,22 +705,31 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
|||||||
.into_compile_error()
|
.into_compile_error()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let model_variant_ty = replace_self(model_ty.clone(), &ty);
|
||||||
model_variants.extend(quote_spanned! { name.span() =>
|
model_variants.extend(quote_spanned! { name.span() =>
|
||||||
#name(patch_db::Model<#ty>),
|
#name(#model_variant_ty),
|
||||||
|
});
|
||||||
|
ref_model_variants.extend(quote_spanned! { name.span() =>
|
||||||
|
#name(&'a #model_variant_ty),
|
||||||
});
|
});
|
||||||
mut_model_variants.extend(quote_spanned! { name.span() =>
|
mut_model_variants.extend(quote_spanned! { name.span() =>
|
||||||
#name(&'a mut patch_db::Model<#ty>),
|
#name(&'a mut #model_variant_ty),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let name = &base.ident;
|
let name = &base.ident;
|
||||||
let vis = &base.vis;
|
let vis = &base.vis;
|
||||||
quote! {
|
quote! {
|
||||||
impl patch_db::HasModel for #name {}
|
impl patch_db::HasModel for #name {
|
||||||
|
type Model = #model_ty;
|
||||||
|
}
|
||||||
|
|
||||||
impl patch_db::Model<#name> {
|
impl #model_ty_name {
|
||||||
#vis fn matchable(&self) -> #match_name {
|
#vis fn into_match(self) -> #match_name {
|
||||||
#impl_new
|
#impl_new
|
||||||
}
|
}
|
||||||
|
#vis fn from_match(value: #match_name) -> Self {
|
||||||
|
#impl_unmatch
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#vis enum #match_name {
|
#vis enum #match_name {
|
||||||
@@ -452,8 +737,19 @@ fn build_model_enum(base: &DeriveInput, ast: &DataEnum) -> TokenStream {
|
|||||||
Error(patch_db::Value),
|
Error(patch_db::Value),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl patch_db::Model<#name> {
|
impl #model_ty_name {
|
||||||
#vis fn matchable<'a>(&'a self) -> #match_name_mut<'a> {
|
#vis fn as_match<'a>(&'a self) -> #match_name_ref<'a> {
|
||||||
|
#impl_new_ref
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#vis enum #match_name_ref<'a> {
|
||||||
|
#ref_model_variants
|
||||||
|
Error(&'a patch_db::Value),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #model_ty_name {
|
||||||
|
#vis fn as_match_mut<'a>(&'a mut self) -> #match_name_mut<'a> {
|
||||||
#impl_new_mut
|
#impl_new_mut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ mod test;
|
|||||||
|
|
||||||
pub use imbl_value as value;
|
pub use imbl_value as value;
|
||||||
pub use imbl_value::Value;
|
pub use imbl_value::Value;
|
||||||
pub use model::{HasModel, Map, Model, ModelExt};
|
pub use model::{HasModel, Model, ModelExt};
|
||||||
pub use patch::{DiffPatch, Dump, Revision};
|
pub use patch::{DiffPatch, Dump, Revision};
|
||||||
pub use patch_db_macro::HasModel;
|
pub use patch_db_macro::HasModel;
|
||||||
pub use store::{PatchDb, Store};
|
pub use store::{PatchDb, Store};
|
||||||
|
use tokio::sync::TryLockError;
|
||||||
pub use {json_patch, json_ptr};
|
pub use {json_patch, json_ptr};
|
||||||
|
|
||||||
pub type Subscriber = tokio::sync::mpsc::UnboundedReceiver<Arc<Revision>>;
|
pub type Subscriber = tokio::sync::mpsc::UnboundedReceiver<Arc<Revision>>;
|
||||||
@@ -47,5 +48,7 @@ pub enum Error {
|
|||||||
#[error("Node Does Not Exist: {0}")]
|
#[error("Node Does Not Exist: {0}")]
|
||||||
NodeDoesNotExist(JsonPointer),
|
NodeDoesNotExist(JsonPointer),
|
||||||
#[error("Provided Function Panicked! {0}")]
|
#[error("Provided Function Panicked! {0}")]
|
||||||
Panick(String),
|
Panic(String),
|
||||||
|
#[error("Would Block")]
|
||||||
|
WouldBlock(#[from] TryLockError),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,193 +1,179 @@
|
|||||||
use std::marker::PhantomData;
|
use imbl_value::Value;
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
|
|
||||||
use imbl::OrdSet;
|
pub trait HasModel: Sized {
|
||||||
use imbl_value::{InternedString, Value};
|
type Model: Model<Self>;
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
pub trait HasModel {}
|
|
||||||
|
|
||||||
/// &mut Model<T> <=> &mut Value
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Model<T> {
|
|
||||||
value: Value,
|
|
||||||
phantom: PhantomData<T>,
|
|
||||||
}
|
}
|
||||||
impl<T> Clone for Model<T> {
|
|
||||||
fn clone(&self) -> Self {
|
mod sealed {
|
||||||
Self {
|
use super::*;
|
||||||
value: self.value.clone(),
|
pub trait ModelMarker {
|
||||||
phantom: PhantomData,
|
fn into_value(self) -> Value;
|
||||||
|
fn from_value(value: Value) -> Self;
|
||||||
|
fn as_value<'a>(&'a self) -> &'a Value;
|
||||||
|
fn value_as<'a>(value: &'a Value) -> &'a Self;
|
||||||
|
fn as_value_mut<'a>(&'a mut self) -> &'a mut Value;
|
||||||
|
fn value_as_mut<'a>(value: &'a mut Value) -> &'a mut Self;
|
||||||
|
}
|
||||||
|
impl<T> ModelMarker for T
|
||||||
|
where
|
||||||
|
T: From<Value> + Into<Value> + Sized,
|
||||||
|
for<'a> &'a T: From<&'a Value> + Into<&'a Value>,
|
||||||
|
for<'a> &'a mut T: From<&'a mut Value> + Into<&'a mut Value>,
|
||||||
|
{
|
||||||
|
fn into_value(self) -> Value {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
fn from_value(value: Value) -> Self {
|
||||||
|
value.into()
|
||||||
|
}
|
||||||
|
fn as_value<'a>(&'a self) -> &'a Value {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
fn value_as<'a>(value: &'a Value) -> &'a Self {
|
||||||
|
value.into()
|
||||||
|
}
|
||||||
|
fn as_value_mut<'a>(&'a mut self) -> &'a mut Value {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
fn value_as_mut<'a>(value: &'a mut Value) -> &'a mut Self {
|
||||||
|
value.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> Deref for Model<T> {
|
|
||||||
type Target = Value;
|
pub trait Model<T>: sealed::ModelMarker + Sized {
|
||||||
fn deref(&self) -> &Self::Target {
|
type Model<U>: Model<U>;
|
||||||
&self.value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl<T> DerefMut for Model<T> {
|
pub trait ModelExt<T>: Model<T> {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn into_value(self) -> Value {
|
||||||
&mut self.value
|
<Self as sealed::ModelMarker>::into_value(self)
|
||||||
}
|
}
|
||||||
}
|
fn from_value(value: Value) -> Self {
|
||||||
impl<T> Model<T> {
|
<Self as sealed::ModelMarker>::from_value(value)
|
||||||
pub fn new(value: Value) -> Self {
|
|
||||||
Self {
|
|
||||||
value,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pub fn into_inner(self) -> Value {
|
fn as_value<'a>(&'a self) -> &'a Value {
|
||||||
self.value
|
<Self as sealed::ModelMarker>::as_value(self)
|
||||||
}
|
}
|
||||||
pub fn transmute<U>(self, f: impl FnOnce(Value) -> Value) -> Model<U> {
|
fn value_as<'a>(value: &'a Value) -> &'a Self {
|
||||||
Model {
|
<Self as sealed::ModelMarker>::value_as(value)
|
||||||
value: f(self.value),
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pub fn transmute_mut<'a, U>(
|
fn as_value_mut<'a>(&'a mut self) -> &'a mut Value {
|
||||||
|
<Self as sealed::ModelMarker>::as_value_mut(self)
|
||||||
|
}
|
||||||
|
fn value_as_mut<'a>(value: &'a mut Value) -> &'a mut Self {
|
||||||
|
<Self as sealed::ModelMarker>::value_as_mut(value)
|
||||||
|
}
|
||||||
|
fn transmute<U>(self, f: impl FnOnce(Value) -> Value) -> Self::Model<U> {
|
||||||
|
Self::Model::<U>::from_value(f(<Self as sealed::ModelMarker>::into_value(self)))
|
||||||
|
}
|
||||||
|
fn transmute_ref<'a, U>(
|
||||||
|
&'a self,
|
||||||
|
f: impl for<'b> FnOnce(&'b Value) -> &'b Value,
|
||||||
|
) -> &'a Self::Model<U> {
|
||||||
|
Self::Model::<U>::value_as(f(<Self as sealed::ModelMarker>::as_value(self)))
|
||||||
|
}
|
||||||
|
fn transmute_mut<'a, U>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
f: impl for<'c> FnOnce(&'c mut Value) -> &'c mut Value,
|
f: impl for<'b> FnOnce(&'b mut Value) -> &'b mut Value,
|
||||||
) -> &'a mut Model<U> {
|
) -> &'a mut Self::Model<U> {
|
||||||
unsafe {
|
Self::Model::<U>::value_as_mut(f(<Self as sealed::ModelMarker>::as_value_mut(self)))
|
||||||
std::mem::transmute::<&'a mut Value, &'a mut Model<U>>(f(std::mem::transmute::<
|
|
||||||
&'a mut Model<T>,
|
|
||||||
&'a mut Value,
|
|
||||||
>(self)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ModelExt: Deref<Target = Value> + DerefMut {
|
|
||||||
type T: DeserializeOwned + Serialize;
|
|
||||||
fn set(&mut self, value: &Self::T) -> Result<(), imbl_value::Error> {
|
|
||||||
*self.deref_mut() = imbl_value::to_value(value)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn get(&self) -> Result<Self::T, imbl_value::Error> {
|
|
||||||
imbl_value::from_value(self.deref().clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: DeserializeOwned + Serialize> ModelExt for Model<T> {
|
|
||||||
type T = T;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Model<Option<T>> {
|
|
||||||
pub fn transpose(self) -> Option<Model<T>> {
|
|
||||||
if self.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(self.transmute(|a| a))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Map: DeserializeOwned + Serialize {
|
|
||||||
type Key;
|
|
||||||
type Value;
|
|
||||||
type Error: From<imbl_value::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Map> Model<T>
|
|
||||||
where
|
|
||||||
T::Key: DeserializeOwned + Ord + Clone,
|
|
||||||
{
|
|
||||||
pub fn keys(&self) -> Result<OrdSet<T::Key>, T::Error> {
|
|
||||||
use serde::de::Error;
|
|
||||||
use serde::Deserialize;
|
|
||||||
match &**self {
|
|
||||||
Value::Object(o) => o
|
|
||||||
.keys()
|
|
||||||
.cloned()
|
|
||||||
.map(|k| {
|
|
||||||
T::Key::deserialize(imbl_value::de::InternedStringDeserializer::from(k))
|
|
||||||
.map_err(|e| {
|
|
||||||
imbl_value::Error {
|
|
||||||
kind: imbl_value::ErrorKind::Deserialization,
|
|
||||||
source: e,
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
v => Err(imbl_value::Error {
|
|
||||||
source: imbl_value::ErrorSource::custom(format!("expected object found {v}")),
|
|
||||||
kind: imbl_value::ErrorKind::Deserialization,
|
|
||||||
}
|
|
||||||
.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: Map> Model<T>
|
|
||||||
where
|
|
||||||
T::Key: AsRef<str>,
|
|
||||||
{
|
|
||||||
pub fn idx(self, key: &T::Key) -> Option<Model<T::Value>> {
|
|
||||||
match &*self {
|
|
||||||
Value::Object(o) if o.contains_key(key.as_ref()) => Some(self.transmute(|v| {
|
|
||||||
use imbl_value::index::Index;
|
|
||||||
key.as_ref().index_into_owned(v).unwrap()
|
|
||||||
})),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: Map> Model<T>
|
|
||||||
where
|
|
||||||
T::Key: AsRef<str>,
|
|
||||||
T::Value: Serialize,
|
|
||||||
{
|
|
||||||
pub fn insert(&mut self, key: &T::Key, value: &T::Value) -> Result<(), T::Error> {
|
|
||||||
use serde::ser::Error;
|
|
||||||
let v = imbl_value::to_value(value)?;
|
|
||||||
match &mut **self {
|
|
||||||
Value::Object(o) => {
|
|
||||||
o.insert(InternedString::intern(key.as_ref()), v);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
_ => Err(imbl_value::Error {
|
|
||||||
source: imbl_value::ErrorSource::custom(format!("expected object found {v}")),
|
|
||||||
kind: imbl_value::ErrorKind::Serialization,
|
|
||||||
}
|
|
||||||
.into()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<T, M: Model<T>> ModelExt<T> for M {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use crate as patch_db;
|
use crate as patch_db;
|
||||||
use imbl_value::json;
|
use crate::model::sealed::ModelMarker;
|
||||||
|
use imbl_value::{from_value, json, to_value, Value};
|
||||||
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
/// &mut Model<T> <=> &mut Value
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Model<T> {
|
||||||
|
value: Value,
|
||||||
|
phantom: PhantomData<T>,
|
||||||
|
}
|
||||||
|
impl<T: DeserializeOwned> Model<T> {
|
||||||
|
pub fn de(self) -> Result<T, imbl_value::Error> {
|
||||||
|
from_value(self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: Serialize> Model<T> {
|
||||||
|
pub fn ser(&mut self, value: &T) -> Result<(), imbl_value::Error> {
|
||||||
|
self.value = to_value(value)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> Clone for Model<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
value: self.value.clone(),
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> From<Value> for Model<T> {
|
||||||
|
fn from(value: Value) -> Self {
|
||||||
|
Self {
|
||||||
|
value,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> From<Model<T>> for Value {
|
||||||
|
fn from(value: Model<T>) -> Self {
|
||||||
|
value.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T> From<&'a Value> for &'a Model<T> {
|
||||||
|
fn from(value: &'a Value) -> Self {
|
||||||
|
unsafe { std::mem::transmute(value) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T> From<&'a Model<T>> for &'a Value {
|
||||||
|
fn from(value: &'a Model<T>) -> Self {
|
||||||
|
unsafe { std::mem::transmute(value) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T> From<&'a mut Value> for &mut Model<T> {
|
||||||
|
fn from(value: &'a mut Value) -> Self {
|
||||||
|
unsafe { std::mem::transmute(value) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T> From<&'a mut Model<T>> for &mut Value {
|
||||||
|
fn from(value: &'a mut Model<T>) -> Self {
|
||||||
|
unsafe { std::mem::transmute(value) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> patch_db::Model<T> for Model<T> {
|
||||||
|
type Model<U> = Model<U>;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(crate::HasModel)]
|
#[derive(crate::HasModel)]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
// #[macro_debug]
|
// #[macro_debug]
|
||||||
struct Foo {
|
struct Foo {
|
||||||
a: Bar,
|
a: Bar,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(crate::HasModel)]
|
#[derive(crate::HasModel)]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
struct Bar {
|
struct Bar {
|
||||||
b: String,
|
b: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mutate_fn(v: &mut Model<Foo>) {
|
fn mutate_fn(v: &mut Model<Foo>) {
|
||||||
let mut a = v.as_a_mut();
|
let mut a = v.as_a_mut();
|
||||||
a.as_b_mut().set(&"NotThis".into()).unwrap();
|
a.as_b_mut().ser(&"NotThis".into()).unwrap();
|
||||||
a.as_b_mut().set(&"Replaced".into()).unwrap();
|
a.as_b_mut().ser(&"Replaced".into()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
let mut model = Model::<Foo>::new(imbl_value::json!({
|
let mut model = Model::<Foo>::from(imbl_value::json!({
|
||||||
"a": {
|
"a": {
|
||||||
"b": "ReplaceMe"
|
"b": "ReplaceMe"
|
||||||
}
|
}
|
||||||
@@ -195,8 +181,8 @@ mod test {
|
|||||||
mutate_fn(&mut model);
|
mutate_fn(&mut model);
|
||||||
mutate_fn(&mut model);
|
mutate_fn(&mut model);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&*model,
|
model.as_value(),
|
||||||
&imbl_value::json!({
|
&json!({
|
||||||
"a": {
|
"a": {
|
||||||
"b": "Replaced"
|
"b": "Replaced"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use fd_lock_rs::FdLock;
|
use fd_lock_rs::FdLock;
|
||||||
|
use futures::FutureExt;
|
||||||
use imbl_value::{InternedString, Value};
|
use imbl_value::{InternedString, Value};
|
||||||
use json_patch::PatchError;
|
use json_patch::PatchError;
|
||||||
use json_ptr::{JsonPointer, SegList};
|
use json_ptr::{JsonPointer, SegList};
|
||||||
@@ -360,7 +361,7 @@ impl PatchDb {
|
|||||||
let mut store = self.store.write().await;
|
let mut store = self.store.write().await;
|
||||||
let old = store.persistent.clone();
|
let old = store.persistent.clone();
|
||||||
let (new, res) = std::panic::catch_unwind(move || f(old)).map_err(|e| {
|
let (new, res) = std::panic::catch_unwind(move || f(old)).map_err(|e| {
|
||||||
Error::Panick(
|
Error::Panic(
|
||||||
e.downcast()
|
e.downcast()
|
||||||
.map(|a| *a)
|
.map(|a| *a)
|
||||||
.unwrap_or_else(|_| "UNKNOWN".to_owned()),
|
.unwrap_or_else(|_| "UNKNOWN".to_owned()),
|
||||||
@@ -370,4 +371,33 @@ impl PatchDb {
|
|||||||
store.apply(diff).await?;
|
store.apply(diff).await?;
|
||||||
Ok((new, res))
|
Ok((new, res))
|
||||||
}
|
}
|
||||||
|
pub async fn run_idempotent<F, Fut, T, E>(&self, f: F) -> Result<(Value, T), E>
|
||||||
|
where
|
||||||
|
F: Fn(Value) -> Fut + Send + Sync + UnwindSafe,
|
||||||
|
for<'a> &'a F: UnwindSafe,
|
||||||
|
Fut: std::future::Future<Output = Result<(Value, T), E>> + UnwindSafe,
|
||||||
|
E: From<Error>,
|
||||||
|
{
|
||||||
|
let store = self.store.read().await;
|
||||||
|
let old = store.persistent.clone();
|
||||||
|
drop(store);
|
||||||
|
loop {
|
||||||
|
let (new, res) = async { f(old.clone()).await }
|
||||||
|
.catch_unwind()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
Error::Panic(
|
||||||
|
e.downcast()
|
||||||
|
.map(|a| *a)
|
||||||
|
.unwrap_or_else(|_| "UNKNOWN".to_owned()),
|
||||||
|
)
|
||||||
|
})??;
|
||||||
|
let mut store = self.store.write().await;
|
||||||
|
if &old == &store.persistent {
|
||||||
|
let diff = diff(&store.persistent, &new);
|
||||||
|
store.apply(diff).await?;
|
||||||
|
return Ok((new, res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use imbl_value::Value;
|
use imbl_value::{json, Value};
|
||||||
use json_ptr::JsonPointer;
|
use json_ptr::JsonPointer;
|
||||||
use patch_db::{HasModel, PatchDb, Revision};
|
use patch_db::{HasModel, PatchDb, Revision};
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
@@ -15,14 +15,14 @@ async fn init_db(db_name: String) -> PatchDb {
|
|||||||
let db = PatchDb::open(db_name).await.unwrap();
|
let db = PatchDb::open(db_name).await.unwrap();
|
||||||
db.put(
|
db.put(
|
||||||
&JsonPointer::<&'static str>::default(),
|
&JsonPointer::<&'static str>::default(),
|
||||||
&Sample {
|
&json!({
|
||||||
a: "test1".to_string(),
|
"a": "test1",
|
||||||
b: Child {
|
"b": {
|
||||||
a: "test2".to_string(),
|
"a": "test2",
|
||||||
b: 1,
|
"b": 1,
|
||||||
c: NewType(None),
|
"c": null,
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -71,19 +71,19 @@ proptest! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, HasModel)]
|
// #[derive(Debug, serde::Deserialize, serde::Serialize, HasModel)]
|
||||||
pub struct Sample {
|
// pub struct Sample {
|
||||||
a: String,
|
// a: String,
|
||||||
#[model]
|
// #[model]
|
||||||
b: Child,
|
// b: Child,
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, HasModel)]
|
// #[derive(Debug, serde::Deserialize, serde::Serialize, HasModel)]
|
||||||
pub struct Child {
|
// pub struct Child {
|
||||||
a: String,
|
// a: String,
|
||||||
b: usize,
|
// b: usize,
|
||||||
c: NewType,
|
// c: NewType,
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, HasModel)]
|
// #[derive(Debug, serde::Deserialize, serde::Serialize, HasModel)]
|
||||||
pub struct NewType(Option<Box<Sample>>);
|
// pub struct NewType(Option<Box<Sample>>);
|
||||||
|
|||||||
Reference in New Issue
Block a user