Add Runnable and Emptiable trait and derive
This commit is contained in:
parent
387c043367
commit
8b3aebb4e9
16 changed files with 285 additions and 20 deletions
|
@ -1,11 +1,11 @@
|
||||||
mod trusted_peer;
|
mod trusted_node;
|
||||||
mod record_deletion;
|
mod record_deletion;
|
||||||
|
|
||||||
pub use trusted_peer::{
|
pub use trusted_node::{
|
||||||
ActiveModel as TrustedPeerActiveModel,
|
ActiveModel as TrustedNodeActiveModel,
|
||||||
Column as TrustedPeerColumn,
|
Column as TrustedNodeColumn,
|
||||||
Entity as TrustedPeerEntity,
|
Entity as TrustedNodeEntity,
|
||||||
Model as TrustedPeerModel,
|
Model as TrustedNodeModel,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use record_deletion::{
|
pub use record_deletion::{
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::data::value::PeerIdValue;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||||
#[sea_orm(table_name = "trusted_peer")]
|
#[sea_orm(table_name = "trusted_node")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
|
@ -8,20 +8,20 @@ pub struct Migration;
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl MigrationTrait for Migration {
|
impl MigrationTrait for Migration {
|
||||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
TrustedPeer::up(manager).await?;
|
TrustedNode::up(manager).await?;
|
||||||
RecordDeletion::up(manager).await?;
|
RecordDeletion::up(manager).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
TrustedPeer::down(manager).await?;
|
TrustedNode::down(manager).await?;
|
||||||
RecordDeletion::down(manager).await?;
|
RecordDeletion::down(manager).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(DeriveIden)]
|
#[derive(DeriveIden)]
|
||||||
enum TrustedPeer {
|
enum TrustedNode {
|
||||||
Table,
|
Table,
|
||||||
Id,
|
Id,
|
||||||
CreatedAt,
|
CreatedAt,
|
||||||
|
@ -33,7 +33,7 @@ enum TrustedPeer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl TableMigration for TrustedPeer {
|
impl TableMigration for TrustedNode {
|
||||||
async fn up<'a>(manager: &'a SchemaManager<'a>) -> Result<(), DbErr> {
|
async fn up<'a>(manager: &'a SchemaManager<'a>) -> Result<(), DbErr> {
|
||||||
manager.create_table(
|
manager.create_table(
|
||||||
Table::create()
|
Table::create()
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
pub mod async_convert;
|
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
|
@ -10,3 +9,4 @@ pub mod migration;
|
||||||
pub mod p2p;
|
pub mod p2p;
|
||||||
#[cfg(any(test, feature="test"))]
|
#[cfg(any(test, feature="test"))]
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
pub mod utils;
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod node;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use uuid::Uuid;
|
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 {
|
pub trait Message: DeserializeOwned + Sized + Serialize {
|
||||||
fn into_writer<W: std::io::Write>(&self, writer: W) -> Result<(), ciborium::ser::Error<std::io::Error>> {
|
fn into_writer<W: std::io::Write>(&self, writer: W) -> Result<(), ciborium::ser::Error<std::io::Error>> {
|
||||||
|
|
|
@ -2,9 +2,9 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct ListDeviceRequest;
|
pub struct ListTrustedNodeRequest;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct ListDeviceResponse {
|
pub struct ListTrustedNodeResponse {
|
||||||
node: Vec<crate::data::entity::TrustedNode>
|
node: Vec<crate::data::entity::TrustedNodeModel>
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
#[derive(thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum P2pError {
|
pub enum P2pError {
|
||||||
|
|
||||||
}
|
}
|
51
lazy-supplements-core/src/utils/emptiable.rs
Normal file
51
lazy-supplements-core/src/utils/emptiable.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
3
lazy-supplements-core/src/utils/mod.rs
Normal file
3
lazy-supplements-core/src/utils/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod async_convert;
|
||||||
|
pub mod emptiable;
|
||||||
|
pub mod runnable;
|
3
lazy-supplements-core/src/utils/runnable.rs
Normal file
3
lazy-supplements-core/src/utils/runnable.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub trait Runnable {
|
||||||
|
async fn run(self);
|
||||||
|
}
|
|
@ -19,4 +19,5 @@ syn = { version = "2.0.104", features = ["full"] }
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
lazy-supplements-core.workspace = true
|
lazy-supplements-core.workspace = true
|
||||||
sea-orm.workspace = true
|
sea-orm.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
67
lazy-supplements-macros/src/derive.rs
Normal file
67
lazy-supplements-macros/src/derive.rs
Normal 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()
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
|
mod derive;
|
||||||
|
|
||||||
use heck::ToUpperCamelCase;
|
use heck::ToUpperCamelCase;
|
||||||
use proc_macro::{self, TokenStream};
|
use proc_macro::{self, TokenStream};
|
||||||
use proc_macro2::Span;
|
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, DataStruct, DeriveInput, Expr, ExprTuple, Field, Fields, FieldsNamed, Ident};
|
||||||
|
use derive::*;
|
||||||
|
|
||||||
#[proc_macro_derive(SyncableModel, attributes(syncable))]
|
#[proc_macro_derive(SyncableModel, attributes(syncable))]
|
||||||
pub fn syncable_model(input: TokenStream) -> TokenStream {
|
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()
|
return fields.pop().unwrap()
|
||||||
} else {
|
} else {
|
||||||
panic!("Model must need one {} field attribute", attribute_arg);
|
panic!("Model must need one {} field attribute", attribute_arg);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_field_idents<'a>(fields: &'a FieldsNamed, attribute_arg: &'static str) -> Vec<&'a Ident>{
|
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.")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
26
lazy-supplements-macros/tests/derive_emptiable.rs
Normal file
26
lazy-supplements-macros/tests/derive_emptiable.rs
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
32
lazy-supplements-macros/tests/derive_runnable.rs
Normal file
32
lazy-supplements-macros/tests/derive_runnable.rs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue