Add test for derive and new trait method about author id
This commit is contained in:
parent
0e1227da85
commit
72c9710283
7 changed files with 111 additions and 50 deletions
|
@ -10,11 +10,13 @@ license = "MIT OR Apache-2.0"
|
||||||
repository = "https://forgejo.fireturlte.net/lazy-supplements"
|
repository = "https://forgejo.fireturlte.net/lazy-supplements"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
chrono = "0.4.41"
|
||||||
ciborium = "0.2.2"
|
ciborium = "0.2.2"
|
||||||
clap = { version = "4.5.38", features = ["derive"] }
|
clap = { version = "4.5.38", features = ["derive"] }
|
||||||
dioxus = { version = "0.6.0", features = [] }
|
dioxus = { version = "0.6.0", features = [] }
|
||||||
lazy-supplements-core.path = "lazy-supplements-core"
|
lazy-supplements-core.path = "lazy-supplements-core"
|
||||||
libp2p = { version = "0.55.0", features = ["macros", "mdns", "noise", "ping", "tcp", "tokio", "yamux" ] }
|
libp2p = { version = "0.55.0", features = ["macros", "mdns", "noise", "ping", "tcp", "tokio", "yamux" ] }
|
||||||
|
sea-orm = { version = "1.1.11", features = ["sqlx-sqlite", "runtime-tokio-native-tls", "macros", "with-chrono", "with-uuid"] }
|
||||||
sea-orm-migration = { version = "1.1.0", features = ["runtime-tokio-rustls", "sqlx-postgres"] }
|
sea-orm-migration = { version = "1.1.0", features = ["runtime-tokio-rustls", "sqlx-postgres"] }
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
|
|
|
@ -8,21 +8,23 @@ repository.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
desktop = ["dep:clap"]
|
desktop = ["dep:clap", "macros"]
|
||||||
test = ["dep:tempfile"]
|
mobile = ["macros"]
|
||||||
|
macros = ["dep:lazy-supplements-macros"]
|
||||||
|
test = ["dep:tempfile", "macros"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
chrono = "0.4.41"
|
chrono.workspace = true
|
||||||
chrono-tz = "0.10.3"
|
chrono-tz = "0.10.3"
|
||||||
ciborium.workspace = true
|
ciborium.workspace = true
|
||||||
clap = {workspace = true, optional = true}
|
clap = {workspace = true, optional = true}
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
lazy-supplements-macros.path = "../lazy-supplements-macros"
|
lazy-supplements-macros = { path = "../lazy-supplements-macros", optional = true }
|
||||||
libp2p.workspace = true
|
libp2p.workspace = true
|
||||||
libp2p-core = { version = "0.43.0", features = ["serde"] }
|
libp2p-core = { version = "0.43.0", features = ["serde"] }
|
||||||
libp2p-identity = { version = "0.2.11", features = ["ed25519", "peerid", "rand", "serde"] }
|
libp2p-identity = { version = "0.2.11", features = ["ed25519", "peerid", "rand", "serde"] }
|
||||||
sea-orm = { version = "1.1.11", features = ["sqlx-sqlite", "runtime-tokio-native-tls", "macros", "with-chrono", "with-uuid"] }
|
sea-orm.workspace = true
|
||||||
sea-orm-migration.workspace = true
|
sea-orm-migration.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
tempfile = { version = "3.20.0", optional = true }
|
tempfile = { version = "3.20.0", optional = true }
|
||||||
|
|
|
@ -7,14 +7,15 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::data::syncable::*;
|
use crate::data::syncable::*;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, SyncableModel)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature="macros", derive(SyncableModel))]
|
||||||
#[sea_orm(table_name = "record_deletion")]
|
#[sea_orm(table_name = "record_deletion")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
#[syncable(uuid)]
|
#[cfg_attr(feature="macros", syncable(uuid))]
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
#[sea_orm(indexed)]
|
#[sea_orm(indexed)]
|
||||||
#[syncable(timestamp)]
|
#[cfg_attr(feature="macros", syncable(timestamp))]
|
||||||
pub created_at: DateTimeUtc,
|
pub created_at: DateTimeUtc,
|
||||||
pub table_name: String,
|
pub table_name: String,
|
||||||
pub record_id: Uuid,
|
pub record_id: Uuid,
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use sea_orm::{*, prelude::*, query::*};
|
use sea_orm::{prelude::*, query::*, sea_query::SimpleExpr, *};
|
||||||
|
#[cfg(feature="macros")]
|
||||||
pub use lazy_supplements_macros::SyncableModel;
|
pub use lazy_supplements_macros::SyncableModel;
|
||||||
pub trait SyncableModel: ModelTrait<Entity = Self::SyncableEntity> {
|
pub trait SyncableModel: ModelTrait<Entity = Self::SyncableEntity> {
|
||||||
type SyncableEntity: SyncableEntity<SyncableModel = Self>;
|
type SyncableEntity: SyncableEntity<SyncableModel = Self>;
|
||||||
fn get_timestamp(&self) -> DateTimeUtc;
|
fn get_timestamp(&self) -> DateTimeUtc;
|
||||||
fn get_uuid(&self) -> Uuid;
|
fn get_id(&self) -> Uuid;
|
||||||
|
fn get_author_id(&self) -> Uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SyncableEntity: EntityTrait<
|
pub trait SyncableEntity: EntityTrait<
|
||||||
|
@ -15,9 +17,17 @@ pub trait SyncableEntity: EntityTrait<
|
||||||
type SyncableActiveModel: SyncableActiveModel<SyncableEntity= Self>;
|
type SyncableActiveModel: SyncableActiveModel<SyncableEntity= Self>;
|
||||||
type SyncableColumn: SyncableColumn;
|
type SyncableColumn: SyncableColumn;
|
||||||
|
|
||||||
async fn get_updated_after(date: DateTimeUtc, db: &DatabaseConnection) -> Result<Vec<<Self as EntityTrait>::Model>, SyncableError> {
|
async fn get_updated(from: DateTimeUtc,until: DateTimeUtc, db: &DatabaseConnection) -> Result<Vec<<Self as EntityTrait>::Model>, SyncableError> {
|
||||||
let result: Vec<Self::SyncableModel> = <Self as EntityTrait>::find()
|
let result: Vec<Self::SyncableModel> = <Self as EntityTrait>::find()
|
||||||
.filter(Self::SyncableColumn::updated_at().gte(date))
|
.filter(Self::SyncableColumn::timestamp_between(from, until))
|
||||||
|
.all(db)
|
||||||
|
.await.unwrap();
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
async fn get_updated_by_author(from: DateTimeUtc, author: Uuid, db: &DatabaseConnection) -> Result<Vec<<Self as EntityTrait>::Model>, SyncableError> {
|
||||||
|
let result: Vec<Self::SyncableModel> = <Self as EntityTrait>::find()
|
||||||
|
.filter(Self::SyncableColumn::timestamp_between(from, until))
|
||||||
|
.filter(Self::SyncableColumn::author_eq(author))
|
||||||
.all(db)
|
.all(db)
|
||||||
.await.unwrap();
|
.await.unwrap();
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
@ -30,15 +40,18 @@ pub trait SyncableEntity: EntityTrait<
|
||||||
pub trait SyncableActiveModel: ActiveModelTrait<Entity = Self::SyncableEntity> {
|
pub trait SyncableActiveModel: ActiveModelTrait<Entity = Self::SyncableEntity> {
|
||||||
|
|
||||||
type SyncableEntity: SyncableEntity<SyncableActiveModel = Self>;
|
type SyncableEntity: SyncableEntity<SyncableActiveModel = Self>;
|
||||||
fn get_uuid(&self) -> Option<Uuid>;
|
fn get_id(&self) -> Option<Uuid>;
|
||||||
fn get_timestamp(&self) -> Option<DateTimeUtc>;
|
fn get_timestamp(&self) -> Option<DateTimeUtc>;
|
||||||
|
fn get_author_id(&self) -> Option<DateTimeUtc>;
|
||||||
fn try_merge(&mut self, other: <Self::SyncableEntity as SyncableEntity>::SyncableModel) -> Result<(), SyncableError> {
|
fn try_merge(&mut self, other: <Self::SyncableEntity as SyncableEntity>::SyncableModel) -> Result<(), SyncableError> {
|
||||||
if self.get_uuid().ok_or(SyncableError::MissingField("uuid"))? != other.get_uuid() {
|
if self.get_uuid().ok_or(SyncableError::MissingField("uuid"))? != other.get_uuid() {
|
||||||
return Err(SyncableError::MismatchUuid)
|
return Err(SyncableError::MismatchUuid)
|
||||||
}
|
}
|
||||||
if self.get_timestamp().ok_or(SyncableError::MissingField("updated_at"))? < other.get_timestamp() {
|
if self.get_timestamp().ok_or(SyncableError::MissingField("updated_at"))? < other.get_timestamp() {
|
||||||
for column in <<<Self as ActiveModelTrait>::Entity as EntityTrait>::Column as Iterable>::iter() {
|
for column in <<<Self as ActiveModelTrait>::Entity as EntityTrait>::Column as Iterable>::iter() {
|
||||||
self.take(column).set_if_not_equals(other.get(column));
|
if column.should_sync(){
|
||||||
|
self.take(column).set_if_not_equals(other.get(column));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -47,10 +60,12 @@ pub trait SyncableActiveModel: ActiveModelTrait<Entity = Self::SyncableEntity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SyncableColumn: ColumnTrait {
|
pub trait SyncableColumn: ColumnTrait {
|
||||||
fn is_uuid(&self) -> bool;
|
fn is_id(&self) -> bool;
|
||||||
fn is_timestamp(&self) -> bool;
|
fn is_timestamp(&self) -> bool;
|
||||||
fn updated_at() -> Self;
|
fn should_sync(&self) -> bool;
|
||||||
fn should_skipped(&self);
|
fn timestamp_between(from: DateTimeUtc, to: DateTimeUtc) -> SimpleExpr;
|
||||||
|
fn author_eq(author_id: Uuid) -> SimpleExpr;
|
||||||
|
fn is_author_id(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,3 +14,9 @@ heck = "0.5.0"
|
||||||
proc-macro2 = "1.0.95"
|
proc-macro2 = "1.0.95"
|
||||||
quote = "1.0.40"
|
quote = "1.0.40"
|
||||||
syn = { version = "2.0.104", features = ["full"] }
|
syn = { version = "2.0.104", features = ["full"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
chrono.workspace = true
|
||||||
|
lazy-supplements-core.workspace = true
|
||||||
|
sea-orm.workspace = true
|
||||||
|
uuid.workspace = true
|
|
@ -4,25 +4,30 @@ use proc_macro2::Span;
|
||||||
use quote::{format_ident, quote, ToTokens};
|
use quote::{format_ident, quote, ToTokens};
|
||||||
use syn::{parse_macro_input, Data, DeriveInput, Expr, ExprTuple, Field, Fields, FieldsNamed, Ident};
|
use syn::{parse_macro_input, Data, DeriveInput, Expr, ExprTuple, Field, Fields, FieldsNamed, Ident};
|
||||||
|
|
||||||
#[proc_macro_derive(SyncableModel)]
|
#[proc_macro_derive(SyncableModel, attributes(syncable))]
|
||||||
pub fn syncable_model(input: TokenStream) -> TokenStream {
|
pub fn syncable_model(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
let struct_name = input.ident;
|
let struct_name = input.ident;
|
||||||
assert_eq!(format_ident!("{}", struct_name), "Model");
|
assert_eq!(format_ident!("{}", struct_name), "Model");
|
||||||
let fields = extract_fields(&input.data);
|
let fields = extract_fields(&input.data);
|
||||||
let uuid_field = extract_uuid_field(&fields);
|
let id_snake = extract_unique_field_ident(&fields, "id");
|
||||||
let uuid_field_camel = Ident::new(&uuid_field.to_string().to_upper_camel_case(), Span::call_site());
|
let id_camel = Ident::new(&id_snake.to_string().to_upper_camel_case(), Span::call_site());
|
||||||
let timestamp_field = extract_timestamp_field(&fields);
|
let timestamp_snake = extract_unique_field_ident(&fields, "timestamp");
|
||||||
let timestamp_field_camel = Ident::new(×tamp_field.to_string().to_upper_camel_case(), Span::call_site());
|
let timestamp_camel = Ident::new(×tamp_snake.to_string().to_upper_camel_case(), Span::call_site());
|
||||||
let skip_fields = extract_skip_fields(&fields);
|
let author_id_snake = extract_unique_field_ident(&fields, "author_id");
|
||||||
|
let author_id_camel = Ident::new(&author_id_snake.to_string().to_upper_camel_case(), Span::call_site());
|
||||||
|
let skips_snake = extract_field_idents(&fields, "skip");
|
||||||
let output = quote!{
|
let output = quote!{
|
||||||
impl SyncableModel for #struct_name {
|
impl SyncableModel for #struct_name {
|
||||||
type SyncableEntity = Entity;
|
type SyncableEntity = Entity;
|
||||||
fn get_uuid(&self) -> Uuid {
|
fn get_id(&self) -> Uuid {
|
||||||
self.#uuid_field
|
self.#id_snake
|
||||||
}
|
}
|
||||||
fn get_timestamp() -> DateTimeUtc {
|
fn get_timestamp() -> DateTimeUtc {
|
||||||
self.#timestamp_field
|
self.#timestamp_snake
|
||||||
|
}
|
||||||
|
fn get_author_id() -> Uuid {
|
||||||
|
self.#timestamp_snake
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl SyncableEntity for Entity {
|
impl SyncableEntity for Entity {
|
||||||
|
@ -33,44 +38,45 @@ pub fn syncable_model(input: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
impl SyncableActiveModel for ActiveModel {
|
impl SyncableActiveModel for ActiveModel {
|
||||||
type SyncableEntity = Entity;
|
type SyncableEntity = Entity;
|
||||||
fn get_uuid(&self) -> Option<Uuid> {
|
fn get_id(&self) -> Option<Uuid> {
|
||||||
self.#uuid_field.into_value()
|
self.#id_snake.into_value()
|
||||||
}
|
}
|
||||||
fn get_timestamp(&self) -> Option<DateTimeUtc> {
|
fn get_timestamp(&self) -> Option<DateTimeUtc> {
|
||||||
self.#timestamp_field.into_value()
|
self.#timestamp_snake.into_value()
|
||||||
}
|
}
|
||||||
}
|
fn get_author_id(&self) -> Option<DateTimeUtc> {
|
||||||
|
self.#author_id_snake.into_value()
|
||||||
|
} }
|
||||||
impl SyncableColumn for Column {
|
impl SyncableColumn for Column {
|
||||||
fn is_uuid(&self) -> bool {
|
fn is_id(&self) -> bool {
|
||||||
self == &Column::#uuid_field_camel
|
matches!(self, Column::#id_camel)
|
||||||
}
|
}
|
||||||
fn is_timestamp(&self) -> bool {
|
fn is_timestamp(&self) -> bool {
|
||||||
self == &Column::#timestamp_field_camel
|
matches!(self, Column::#timestamp_camel)
|
||||||
|
}
|
||||||
|
fn is_author_id(&self) -> bool {
|
||||||
|
matches!(self, Column::#author_id_camel)
|
||||||
|
}
|
||||||
|
fn should_sync(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn timestamp_between(from: DateTimeUtc, until: DateTimeUtc) -> SimpleExpr {
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
output.into()
|
output.into()
|
||||||
}
|
}
|
||||||
fn extract_skip_fields(fields: &FieldsNamed) -> Vec<&Ident> {
|
fn extract_unique_field_ident<'a>(fields: &'a FieldsNamed, attribute_arg: &'static str) -> &'a Ident {
|
||||||
extract_fields_with_attribute(fields, "skip")
|
let mut fields = extract_field_idents(fields, attribute_arg);
|
||||||
}
|
if fields.len() == 1 {
|
||||||
fn extract_timestamp_field(fields: &FieldsNamed) -> &Ident {
|
fields.pop().unwrap()
|
||||||
let mut timestamp_fields = extract_fields_with_attribute(fields, "timestamp");
|
|
||||||
if timestamp_fields.len() == 1 {
|
|
||||||
timestamp_fields.pop().unwrap()
|
|
||||||
} else {
|
} else {
|
||||||
panic!("Model must need one timestamp field attribute")
|
panic!("Model must need one {} field attribute", attribute_arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn extract_uuid_field(fields: &FieldsNamed) -> &Ident {
|
|
||||||
let mut uuid_fields = extract_fields_with_attribute(fields, "uuid");
|
fn extract_field_idents<'a>(fields: &'a FieldsNamed, attribute_arg: &'static str) -> Vec<&'a Ident>{
|
||||||
if uuid_fields.len() == 1 {
|
|
||||||
uuid_fields.pop().unwrap()
|
|
||||||
} else {
|
|
||||||
panic!("Model must need one uuid field attribute")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn extract_fields_with_attribute<'a>(fields: &'a FieldsNamed, attribute_arg: &'static str) -> Vec<&'a Ident>{
|
|
||||||
fields.named.iter()
|
fields.named.iter()
|
||||||
.filter_map(|field| {
|
.filter_map(|field| {
|
||||||
field.attrs.iter()
|
field.attrs.iter()
|
||||||
|
|
29
lazy-supplements-macros/tests/derive_syncable.rs
Normal file
29
lazy-supplements-macros/tests/derive_syncable.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use chrono::Local;
|
||||||
|
use sea_orm::entity::{
|
||||||
|
*,
|
||||||
|
prelude::*
|
||||||
|
};
|
||||||
|
use lazy_supplements_core::data::syncable::*;
|
||||||
|
use lazy_supplements_macros::SyncableModel;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, SyncableModel)]
|
||||||
|
#[sea_orm(table_name = "syncable")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
#[syncable(id)]
|
||||||
|
pub id: Uuid,
|
||||||
|
#[sea_orm(indexed)]
|
||||||
|
#[syncable(timestamp)]
|
||||||
|
pub created_at: DateTimeUtc,
|
||||||
|
pub table_name: String,
|
||||||
|
|
||||||
|
#[syncable(author_id)]
|
||||||
|
pub updated_by: Uuid,
|
||||||
|
pub record_id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)]
|
||||||
|
pub enum Relation{}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
Loading…
Add table
Reference in a new issue