Add Runnable and Emptiable trait and derive

This commit is contained in:
fluo10 2025-07-03 08:28:59 +09:00
parent 387c043367
commit 8b3aebb4e9
16 changed files with 285 additions and 20 deletions

View file

@ -1,11 +1,11 @@
mod trusted_peer;
mod trusted_node;
mod record_deletion;
pub use trusted_peer::{
ActiveModel as TrustedPeerActiveModel,
Column as TrustedPeerColumn,
Entity as TrustedPeerEntity,
Model as TrustedPeerModel,
pub use trusted_node::{
ActiveModel as TrustedNodeActiveModel,
Column as TrustedNodeColumn,
Entity as TrustedNodeEntity,
Model as TrustedNodeModel,
};
pub use record_deletion::{

View file

@ -9,7 +9,7 @@ use crate::data::value::PeerIdValue;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "trusted_peer")]
#[sea_orm(table_name = "trusted_node")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,

View file

@ -8,20 +8,20 @@ pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
TrustedPeer::up(manager).await?;
TrustedNode::up(manager).await?;
RecordDeletion::up(manager).await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
TrustedPeer::down(manager).await?;
TrustedNode::down(manager).await?;
RecordDeletion::down(manager).await?;
Ok(())
}
}
#[derive(DeriveIden)]
enum TrustedPeer {
enum TrustedNode {
Table,
Id,
CreatedAt,
@ -33,7 +33,7 @@ enum TrustedPeer {
}
#[async_trait::async_trait]
impl TableMigration for TrustedPeer {
impl TableMigration for TrustedNode {
async fn up<'a>(manager: &'a SchemaManager<'a>) -> Result<(), DbErr> {
manager.create_table(
Table::create()

View file

@ -1,4 +1,3 @@
pub mod async_convert;
pub mod cache;
pub mod config;
pub mod data;
@ -10,3 +9,4 @@ pub mod migration;
pub mod p2p;
#[cfg(any(test, feature="test"))]
pub mod tests;
pub mod utils;

View file

@ -2,7 +2,7 @@ mod node;
use serde::{de::DeserializeOwned, Serialize};
use uuid::Uuid;
use crate::{async_convert::{AsyncTryFrom, AsyncTryInto}, error::Error};
use crate::{utils::async_convert::{AsyncTryFrom, AsyncTryInto}, error::Error};
pub trait Message: DeserializeOwned + Sized + Serialize {
fn into_writer<W: std::io::Write>(&self, writer: W) -> Result<(), ciborium::ser::Error<std::io::Error>> {

View file

@ -2,9 +2,9 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub struct ListDeviceRequest;
pub struct ListTrustedNodeRequest;
#[derive(Debug, Deserialize, Serialize)]
pub struct ListDeviceResponse {
node: Vec<crate::data::entity::TrustedNode>
pub struct ListTrustedNodeResponse {
node: Vec<crate::data::entity::TrustedNodeModel>
}

View file

@ -1,4 +1,4 @@
#[derive(thiserror::Error)]
#[derive(Debug, thiserror::Error)]
pub enum P2pError {
}

View file

@ -0,0 +1,51 @@
use std::collections::{HashMap, HashSet};
pub trait Emptiable{
fn empty() -> Self;
fn is_empty(&self) -> bool;
}
impl<T> Emptiable for Vec<T> {
fn empty() -> Self {
Self::new()
}
fn is_empty(&self) -> bool {
self.is_empty()
}
}
impl<T> Emptiable for Option<T> {
fn empty() -> Self {
None
}
fn is_empty(&self) -> bool {
self.is_none()
}
}
impl Emptiable for String {
fn empty() -> Self {
String::new()
}
fn is_empty(&self) -> bool {
self.is_empty()
}
}
impl<T, U> Emptiable for HashMap<T, U> {
fn empty() -> Self {
HashMap::new()
}
fn is_empty(&self) -> bool {
self.is_empty()
}
}
impl<T> Emptiable for HashSet<T> {
fn empty() -> Self {
HashSet::new()
}
fn is_empty(&self) -> bool {
self.is_empty()
}
}

View file

@ -0,0 +1,3 @@
pub mod async_convert;
pub mod emptiable;
pub mod runnable;

View file

@ -0,0 +1,3 @@
pub trait Runnable {
async fn run(self);
}

View file

@ -19,4 +19,5 @@ syn = { version = "2.0.104", features = ["full"] }
chrono.workspace = true
lazy-supplements-core.workspace = true
sea-orm.workspace = true
tokio.workspace = true
uuid.workspace = true

View file

@ -0,0 +1,67 @@
use syn::{DataEnum, DataStruct, Fields, FieldsNamed, Ident, Type};
fn extract_fields_named_from_data_struct(data: &DataStruct) -> &FieldsNamed {
match data.fields {
Fields::Named(ref fields) => fields,
_ => panic!("all fields must be named.")
}
}
fn extract_idents_from_fields_named_with_attribute<'a>(fields: &'a FieldsNamed, attribute: &'static str) -> Vec<&'a Ident>{
fields.named.iter()
.filter_map(|field| {
field.attrs.iter()
.find_map(|attr| {
if attr.path().is_ident(attribute) {
field.ident.as_ref()
} else {
None
}
})
}).collect()
}
pub fn extract_idents_and_types_from_data_struct_with_attribute<'a>(data: &'a DataStruct, attribute: &'static str) -> Vec<(Ident, Type)>{
let fields = extract_fields_named_from_data_struct(data);
fields.named.iter().filter_map(|field| {
field.attrs.iter()
.find_map(|attr| {
if attr.path().is_ident(attribute) {
Some((field.ident.clone().unwrap(), field.ty.clone()))
} else {
None
}
})
}).collect()
}
pub fn extract_idents_and_types_from_data_struct<'a>(data: &'a DataStruct) -> Vec<(Ident, Type)>{
let fields = extract_fields_named_from_data_struct(data);
fields.named.iter().map(|x| {(x.ident.clone().unwrap(), x.ty.clone())}).collect()
}
pub fn unwrap_vec_or_panic<T>(mut source: Vec<T>, msg: &'static str) -> T {
if source.len() == 1 {
source.pop().unwrap()
} else {
panic!("{}", msg)
}
}
pub fn extract_idents_and_types_from_enum_struct<'a>(data: &'a DataEnum) -> Vec<(Ident, Type)> {
data.variants.iter().map(|variant| {
let mut fields: Vec<Type> = match &variant.fields {
Fields::Unnamed(fields_unnamed) => {
fields_unnamed.unnamed.iter().map(|x| {
x.ty.clone()
}).collect()
},
_ => panic!("Fields of enum variant must be unnamed!")
};
if fields.len() == 1 {
(variant.ident.clone(), fields.pop().unwrap())
} else {
panic!("Fields of enum variant must be single!")
}
}).collect()
}

View file

@ -1,8 +1,11 @@
mod derive;
use heck::ToUpperCamelCase;
use proc_macro::{self, TokenStream};
use proc_macro2::Span;
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, DataStruct, DeriveInput, Expr, ExprTuple, Field, Fields, FieldsNamed, Ident};
use derive::*;
#[proc_macro_derive(SyncableModel, attributes(syncable))]
pub fn syncable_model(input: TokenStream) -> TokenStream {
@ -78,7 +81,7 @@ fn extract_unique_field_ident<'a>(fields: &'a FieldsNamed, attribute_arg: &'stat
return fields.pop().unwrap()
} else {
panic!("Model must need one {} field attribute", attribute_arg);
}
};
}
fn extract_field_idents<'a>(fields: &'a FieldsNamed, attribute_arg: &'static str) -> Vec<&'a Ident>{
@ -132,3 +135,82 @@ fn extract_fields(data: &Data) -> &FieldsNamed {
}
}
#[proc_macro_derive(Emptiable)]
pub fn emptiable(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let type_ident = input.ident;
match input.data {
Data::Struct(ref data) => {
let field_idents = extract_idents_and_types_from_data_struct(data);
let is_empty_iter = field_idents.iter().map(|(ident, type_name)| {
quote!{
<#type_name as Emptiable>::is_empty(&self.#ident)
}
});
let empty_iter = field_idents.iter().map(|(ident, type_name)| {
quote!{
#ident: <#type_name as Emptiable>::empty(),
}
});
quote!{
impl Emptiable for #type_ident {
fn empty() -> Self {
Self {
#(#empty_iter)*
}
}
fn is_empty(&self) -> bool {
#(#is_empty_iter)&&*
}
}
}.into()
}
Data::Enum(ref fields) => {
todo!()
},
_ => panic!("struct or enum expected, but got union.")
}
}
#[proc_macro_derive(Runnable, attributes(runnable))]
pub fn runnable(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let type_ident = input.ident;
match input.data {
Data::Struct(ref data) => {
let mut idents = extract_idents_and_types_from_data_struct_with_attribute(data, "runnable");
let (field_ident, field_type) = unwrap_vec_or_panic(idents, "Runnable struct must have one field with runnable attribute");
quote!{
impl Runnable for #type_ident {
async fn run(self) {
<#field_type as Runnable>::run(self.#field_ident).await
}
}
}.into()
}
Data::Enum(ref variants) => {
let quote_vec = extract_idents_and_types_from_enum_struct(&variants);
let quote_iter = quote_vec.iter().map(|(variant_ident, variant_type)|{
quote!{
Self::#variant_ident(x) => <#variant_type as Runnable>::run(x).await,
}
});
quote!{
impl Runnable for #type_ident {
async fn run(self) {
match self {
#(#quote_iter)*
}
}
}
}.into()
},
_ => panic!("struct or enum expected, but got union.")
}
}

View file

@ -0,0 +1,26 @@
use std::collections::{HashMap, HashSet};
use lazy_supplements_core::utils::emptiable::Emptiable;
use lazy_supplements_macros::Emptiable;
#[derive(Debug, PartialEq, Emptiable)]
struct EmptiableStruct{
vec: Vec<u8>,
text: String,
map: HashMap<u8, u8>,
set: HashSet<u8>,
opt: Option<u8>,
}
#[cfg(test)]
fn test() {
use std::hash::Hash;
let empty = EmptiableStruct::empty();
assert_eq!(&empty, &EmptiableStruct{
vec: Vec::new(),
text: String::new(),
map: HashMap::new(),
set: HashSet::new(),
opt: None,
})
}

View file

@ -0,0 +1,32 @@
use lazy_supplements_core::utils::runnable::Runnable;
use lazy_supplements_macros::Runnable;
struct RunnableStruct1;
impl Runnable for RunnableStruct1 {
async fn run(self) {
print!("Run {}", stringify!(RunnableStruct1::run()))
}
}
#[derive(Runnable)]
enum RunnableEnum {
Struct1(RunnableStruct1),
}
#[derive(Runnable)]
struct RunnableStruct2 {
#[runnable]
runnable: RunnableEnum,
}
#[tokio::test]
async fn test() {
let runnable = RunnableStruct2{
runnable: RunnableEnum::Struct1(RunnableStruct1)
};
runnable.run().await;
}