Add Mergeable trait and derive

This commit is contained in:
fluo10 2025-07-04 08:05:43 +09:00
parent 8b3aebb4e9
commit 1adebe17e4
17 changed files with 117 additions and 116 deletions

View file

@ -3,31 +3,30 @@ mod storage;
mod p2p;
use std::path::Path;
use crate::error::Error;
use crate::{error::Error, utils::{emptiable::Emptiable, mergeable::Mergeable}};
pub use error::ConfigError;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}};
pub use storage::{StorageConfig, PartialStorageConfig};
pub use p2p::{P2pConfig, PartialP2pConfig};
pub trait PartialConfig: Serialize + Sized + DeserializeOwned
{
fn default() -> Self;
fn empty() -> Self;
fn merge(&mut self, other: Self);
pub trait Config: TryFrom<Self::PartialConfig>{
type PartialConfig: PartialConfig<Config = Self>;
}
pub trait PartialConfig: Emptiable + From<Self::Config> + Mergeable {
type Config: Config<PartialConfig = Self>;
}
pub trait PartialCoreConfig: DeserializeOwned + Serialize {
fn new() -> Self;
fn from_toml(s: &str) -> Result<Self, toml::de::Error> {
toml::from_str(s)
}
fn into_toml(&self) -> Result<String, toml::ser::Error> {
toml::to_string(self)
}
fn is_empty(&self) -> bool;
}
pub trait PartialConfigRoot: DeserializeOwned + Serialize {
fn new() -> Self;
async fn read_or_create<T>(path: T) -> Result<Self, Error>
where
T: AsRef<Path>
@ -67,7 +66,7 @@ pub trait PartialConfigRoot: DeserializeOwned + Serialize {
mod tests {
use serde::{Deserialize, Serialize};
use crate::tests::test_toml_serialize_deserialize;
use crate::{tests::test_toml_serialize_deserialize, utils::{emptiable::Emptiable, mergeable::Mergeable}};
use super::{p2p::{P2pConfig, PartialP2pConfig}, PartialConfig};
@ -77,13 +76,14 @@ mod tests {
p2p: Option<PartialP2pConfig>
}
impl PartialConfig for TestConfig {
impl Default for TestConfig {
fn default() -> Self {
Self {
p2p: Some(PartialP2pConfig::default()),
}
}
}
impl Emptiable for TestConfig {
fn empty() -> Self {
Self {
p2p: None,
@ -93,7 +93,8 @@ mod tests {
fn is_empty(&self) -> bool {
self.p2p.is_none()
}
}
impl Mergeable for TestConfig {
fn merge(&mut self, other: Self) {
if let Some(p2p) = other.p2p {
self.p2p = Some(p2p);

View file

@ -11,7 +11,7 @@ use tracing_subscriber::EnvFilter;
use crate::{
config::PartialConfig,
error::Error, p2p
error::Error, p2p, utils::emptiable::Emptiable
};
static DEFAULT_P2P_LISTEN_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))];
@ -86,6 +86,7 @@ mod keypair_parser {
}
#[cfg_attr(feature="desktop",derive(Args))]
#[cfg_attr(feature="macros", derive(Emptiable))]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct PartialP2pConfig {
#[cfg_attr(feature="desktop",arg(long))]
@ -96,44 +97,10 @@ pub struct PartialP2pConfig {
pub port: Option<u16>,
}
impl PartialP2pConfig {
pub fn with_new_secret(mut self) -> Self {
self.secret = Some(keypair_to_base64(&Keypair::generate_ed25519()));
self
}
pub async fn read_or_create<T>(path: T) -> Result<Self, Error>
where
T: AsRef<Path>
{
if !path.as_ref().exists() {
Self::empty().write_to(&path).await?;
}
Self::read_from(&path).await
}
pub async fn read_from<T>(path:T) -> Result<Self, Error>
where
T: AsRef<Path>
{
let mut file = File::open(path.as_ref()).await?;
let mut content = String::new();
file.read_to_string(&mut content).await?;
let config: Self = toml::from_str(&content)?;
Ok(config)
}
pub async fn write_to<T>(&self, path:T) -> Result<(), Error>
where
T: AsRef<Path>
{
if !path.as_ref().exists() {
if let Some(x) = path.as_ref().parent() {
std::fs::create_dir_all(x)?;
};
let _ = File::create(&path).await?;
}
let mut file = File::create(&path).await?;
file.write_all(toml::to_string(self)?.as_bytes()).await?;
Ok(())
}
}
impl From<P2pConfig> for PartialP2pConfig {
@ -146,29 +113,7 @@ impl From<P2pConfig> for PartialP2pConfig {
}
}
impl PartialConfig for PartialP2pConfig {
fn empty() -> Self {
Self {
secret: None,
listen_ips: None,
port: None,
}
}
fn is_empty(&self) -> bool {
self.secret.is_none() && self.listen_ips.is_none() && self.port.is_none()
}
fn merge(&mut self, another: Self) {
if let Some(x) = another.secret {
self.secret = Some(x);
};
if let Some(x) = another.listen_ips {
self.listen_ips = Some(x);
};
if let Some(x) = another.port {
self.port = Some(x);
};
}
impl Default for PartialP2pConfig {
fn default() -> Self {
Self {
secret: None,

View file

@ -5,7 +5,7 @@ use clap::Args;
#[cfg(any(test, feature="test"))]
use tempfile::tempdir;
use crate::{config::{ConfigError, PartialConfig}};
use crate::{config::{ConfigError, PartialConfig}, utils::emptiable::Emptiable};
use libp2p::mdns::Config;
use serde::{Deserialize, Serialize};
@ -50,6 +50,7 @@ impl TryFrom<PartialStorageConfig> for StorageConfig {
}
}
#[cfg_attr(feature="desktop", derive(Args))]
#[cfg_attr(feature="macros", derive(Emptiable))]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PartialStorageConfig {
#[cfg_attr(feature="desktop", arg(long))]
@ -65,27 +66,4 @@ impl From<StorageConfig> for PartialStorageConfig {
cache_directory: Some(config.cache_directory),
}
}
}
impl PartialConfig for PartialStorageConfig {
fn empty() -> Self {
Self{
data_directory: None,
cache_directory: None,
}
}
fn is_empty(&self) -> bool {
self.data_directory.is_none() && self.cache_directory.is_none()
}
fn default() -> Self {
todo!()
}
fn merge(&mut self, other: Self) {
if let Some(x) = other.data_directory {
self.data_directory = Some(x);
}
if let Some(x) = other.cache_directory {
self.cache_directory = Some(x);
}
}
}

View file

@ -1,4 +1,6 @@
use std::collections::{HashMap, HashSet};
#[cfg(feature="macros")]
pub use lazy_supplements_macros::Emptiable;
pub trait Emptiable{
fn empty() -> Self;

View file

@ -0,0 +1,14 @@
pub trait Mergeable: Sized {
fn merge(&mut self, other: Self);
}
impl<T> Mergeable for Option<T> {
fn merge(&mut self, mut other: Self) {
match other.take() {
Some(x) => {
let _ = self.insert(x);
},
None => {}
};
}
}

View file

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

View file

@ -1,3 +1,6 @@
#[cfg(feature="macros")]
pub use lazy_supplements_macros::Runnable;
pub trait Runnable {
async fn run(self);
}

View file

@ -1,6 +1,7 @@
use clap::Args;
use crate::utils::runnable::Runnable;
use crate::cli::{ConfigArgs, RunnableCommand};
use crate::cli::ConfigArgs;
use crate::cli::PeerArgs;
@ -14,7 +15,7 @@ pub struct DeviceAddCommandArgs {
config: ConfigArgs
}
impl RunnableCommand for DeviceAddCommandArgs {
impl Runnable for DeviceAddCommandArgs {
async fn run(self) {
todo!()
}

View file

@ -1,5 +1,5 @@
use clap::Args;
use crate::utils::runnable::Runnable;
use crate::cli::{ConfigArgs, RunnableCommand};
#[derive(Debug, Args)]
@ -8,7 +8,7 @@ pub struct DeviceListCommandArgs{
config: ConfigArgs
}
impl RunnableCommand for DeviceListCommandArgs {
impl Runnable for DeviceListCommandArgs {
async fn run(self) {
todo!()
}

View file

@ -5,6 +5,7 @@ mod remove;
mod scan;
pub use add::DeviceAddCommandArgs;
use crate::utils::runnable::Runnable;
use libp2p::{Multiaddr, PeerId};
pub use list::DeviceListCommandArgs;
pub use ping::DevicePingCommandArgs;
@ -20,13 +21,14 @@ use crate::{cli::ServerArgs, error::Error};
use super::ConfigArgs;
#[derive(Debug, Args)]
#[derive(Debug, Args, Runnable)]
pub struct DeviceCommandArgs {
#[command(subcommand)]
#[runnable]
pub command: DeviceSubcommand
}
#[derive(Debug, Subcommand)]
#[derive(Debug, Subcommand, Runnable)]
pub enum DeviceSubcommand {
Add(DeviceAddCommandArgs),
List(DeviceListCommandArgs),

View file

@ -1,6 +1,6 @@
use clap::Args;
use crate::cli::{ConfigArgs, PeerArgs, RunnableCommand};
use crate::utils::runnable::Runnable;
use crate::cli::{ConfigArgs, PeerArgs};
#[derive(Debug, Args)]
pub struct DevicePingCommandArgs{
@ -10,7 +10,7 @@ pub struct DevicePingCommandArgs{
config: ConfigArgs
}
impl RunnableCommand for DevicePingCommandArgs {
impl Runnable for DevicePingCommandArgs {
async fn run(self) {
todo!()
}

View file

@ -1,5 +1,5 @@
use clap::Args;
use crate::utils::runnable::Runnable;
use crate::cli::{ConfigArgs, DeviceArgs, RunnableCommand};
#[derive(Debug, Args)]
@ -10,7 +10,7 @@ pub struct DeviceRemoveCommandArgs{
config: ConfigArgs
}
impl RunnableCommand for DeviceRemoveCommandArgs {
impl Runnable for DeviceRemoveCommandArgs {
async fn run(self) {
todo!()
}

View file

@ -1,5 +1,5 @@
use clap::Args;
use crate::utils::runnable::Runnable;
use crate::cli::{ConfigArgs, RunnableCommand};
#[derive(Debug, Args)]
@ -8,7 +8,7 @@ pub struct DeviceScanCommandArgs{
config: ConfigArgs
}
impl RunnableCommand for DeviceScanCommandArgs {
impl Runnable for DeviceScanCommandArgs {
async fn run(self) {
todo!()
}

View file

@ -2,6 +2,7 @@ pub mod cli;
pub mod config;
pub mod global;
pub mod ipc;
pub mod utils;
pub use lazy_supplements_core::{
cache,
data,

View file

@ -0,0 +1 @@
pub use lazy_supplements_core::utils::*;

View file

@ -135,7 +135,6 @@ fn extract_fields(data: &Data) -> &FieldsNamed {
}
}
#[proc_macro_derive(Emptiable)]
pub fn emptiable(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
@ -166,11 +165,32 @@ pub fn emptiable(input: TokenStream) -> TokenStream {
}
}.into()
}
Data::Enum(ref fields) => {
todo!()
_ => panic!("struct or expected, but got other type.")
},
_ => panic!("struct or enum expected, but got union.")
}
}
#[proc_macro_derive(Mergeable)]
pub fn mergeable(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 merge_iter = field_idents.iter().map(|(ident, type_name)| {
quote!{
<#type_name as Mergeable>::merge(&mut self.#ident, other.#ident);
}
});
quote!{
impl Mergeable for #type_ident {
fn merge(&mut self, mut other: Self){
#(#merge_iter)*
}
}
}.into()
}
_ => panic!("struct expected, but got other type.")
}
}

View file

@ -0,0 +1,32 @@
use std::collections::{HashMap, HashSet};
use lazy_supplements_core::utils::mergeable::Mergeable;
use lazy_supplements_macros::Mergeable;
#[derive(Clone, Debug, PartialEq, Mergeable)]
struct MergeableStruct {
opt: Option<u8>,
}
#[cfg(test)]
fn test() {
let zero = MergeableStruct{
opt: Some(0),
};
let one = MergeableStruct {
opt: Some(1),
};
let none = MergeableStruct{
opt: None,
};
let mut zero_with_one = zero.clone();
zero_with_one.merge(one.clone());
let mut none_with_zero = none.clone();
none_with_zero.merge(zero.clone());
let mut zero_with_none = zero.clone();
zero_with_none.merge(none.clone());
assert_eq!(zero_with_one.clone(), one.clone());
assert_eq!(none_with_zero, zero.clone());
assert_eq!(zero_with_none, zero.clone());
}