Add Mergeable trait and derive
This commit is contained in:
parent
8b3aebb4e9
commit
1adebe17e4
17 changed files with 117 additions and 116 deletions
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))]
|
||||
|
@ -66,26 +67,3 @@ impl From<StorageConfig> for PartialStorageConfig {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
#[cfg(feature="macros")]
|
||||
pub use lazy_supplements_macros::Emptiable;
|
||||
|
||||
pub trait Emptiable{
|
||||
fn empty() -> Self;
|
||||
|
|
14
lazy-supplements-core/src/utils/mergeable.rs
Normal file
14
lazy-supplements-core/src/utils/mergeable.rs
Normal 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 => {}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod async_convert;
|
||||
pub mod emptiable;
|
||||
pub mod mergeable;
|
||||
pub mod runnable;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#[cfg(feature="macros")]
|
||||
pub use lazy_supplements_macros::Runnable;
|
||||
|
||||
pub trait Runnable {
|
||||
async fn run(self);
|
||||
}
|
|
@ -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!()
|
||||
}
|
||||
|
|
|
@ -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!()
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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!()
|
||||
}
|
||||
|
|
|
@ -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!()
|
||||
}
|
||||
|
|
|
@ -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!()
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
1
lazy-supplements-desktop/src/utils.rs
Normal file
1
lazy-supplements-desktop/src/utils.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub use lazy_supplements_core::utils::*;
|
|
@ -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.")
|
||||
|
||||
}
|
||||
}
|
||||
|
|
32
lazy-supplements-macros/tests/derive_mergeable.rs
Normal file
32
lazy-supplements-macros/tests/derive_mergeable.rs
Normal 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());
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue