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;
|
mod p2p;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use crate::error::Error;
|
use crate::{error::Error, utils::{emptiable::Emptiable, mergeable::Mergeable}};
|
||||||
pub use error::ConfigError;
|
pub use error::ConfigError;
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
|
||||||
use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}};
|
use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}};
|
||||||
pub use storage::{StorageConfig, PartialStorageConfig};
|
pub use storage::{StorageConfig, PartialStorageConfig};
|
||||||
pub use p2p::{P2pConfig, PartialP2pConfig};
|
pub use p2p::{P2pConfig, PartialP2pConfig};
|
||||||
pub trait PartialConfig: Serialize + Sized + DeserializeOwned
|
|
||||||
{
|
|
||||||
|
|
||||||
fn default() -> Self;
|
pub trait Config: TryFrom<Self::PartialConfig>{
|
||||||
fn empty() -> Self;
|
type PartialConfig: PartialConfig<Config = Self>;
|
||||||
fn merge(&mut self, other: 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> {
|
fn from_toml(s: &str) -> Result<Self, toml::de::Error> {
|
||||||
toml::from_str(s)
|
toml::from_str(s)
|
||||||
}
|
}
|
||||||
fn into_toml(&self) -> Result<String, toml::ser::Error> {
|
fn into_toml(&self) -> Result<String, toml::ser::Error> {
|
||||||
toml::to_string(self)
|
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>
|
async fn read_or_create<T>(path: T) -> Result<Self, Error>
|
||||||
where
|
where
|
||||||
T: AsRef<Path>
|
T: AsRef<Path>
|
||||||
|
@ -67,7 +66,7 @@ pub trait PartialConfigRoot: DeserializeOwned + Serialize {
|
||||||
mod tests {
|
mod tests {
|
||||||
use serde::{Deserialize, Serialize};
|
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};
|
use super::{p2p::{P2pConfig, PartialP2pConfig}, PartialConfig};
|
||||||
|
|
||||||
|
@ -77,13 +76,14 @@ mod tests {
|
||||||
p2p: Option<PartialP2pConfig>
|
p2p: Option<PartialP2pConfig>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialConfig for TestConfig {
|
impl Default for TestConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
p2p: Some(PartialP2pConfig::default()),
|
p2p: Some(PartialP2pConfig::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
impl Emptiable for TestConfig {
|
||||||
fn empty() -> Self {
|
fn empty() -> Self {
|
||||||
Self {
|
Self {
|
||||||
p2p: None,
|
p2p: None,
|
||||||
|
@ -93,7 +93,8 @@ mod tests {
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
self.p2p.is_none()
|
self.p2p.is_none()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
impl Mergeable for TestConfig {
|
||||||
fn merge(&mut self, other: Self) {
|
fn merge(&mut self, other: Self) {
|
||||||
if let Some(p2p) = other.p2p {
|
if let Some(p2p) = other.p2p {
|
||||||
self.p2p = Some(p2p);
|
self.p2p = Some(p2p);
|
||||||
|
|
|
@ -11,7 +11,7 @@ use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::PartialConfig,
|
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))];
|
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="desktop",derive(Args))]
|
||||||
|
#[cfg_attr(feature="macros", derive(Emptiable))]
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
pub struct PartialP2pConfig {
|
pub struct PartialP2pConfig {
|
||||||
#[cfg_attr(feature="desktop",arg(long))]
|
#[cfg_attr(feature="desktop",arg(long))]
|
||||||
|
@ -96,44 +97,10 @@ pub struct PartialP2pConfig {
|
||||||
pub port: Option<u16>,
|
pub port: Option<u16>,
|
||||||
}
|
}
|
||||||
impl PartialP2pConfig {
|
impl PartialP2pConfig {
|
||||||
|
|
||||||
pub fn with_new_secret(mut self) -> Self {
|
pub fn with_new_secret(mut self) -> Self {
|
||||||
self.secret = Some(keypair_to_base64(&Keypair::generate_ed25519()));
|
self.secret = Some(keypair_to_base64(&Keypair::generate_ed25519()));
|
||||||
self
|
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 {
|
impl From<P2pConfig> for PartialP2pConfig {
|
||||||
|
@ -146,29 +113,7 @@ impl From<P2pConfig> for PartialP2pConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialConfig for PartialP2pConfig {
|
impl Default 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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
secret: None,
|
secret: None,
|
||||||
|
|
|
@ -5,7 +5,7 @@ use clap::Args;
|
||||||
|
|
||||||
#[cfg(any(test, feature="test"))]
|
#[cfg(any(test, feature="test"))]
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use crate::{config::{ConfigError, PartialConfig}};
|
use crate::{config::{ConfigError, PartialConfig}, utils::emptiable::Emptiable};
|
||||||
use libp2p::mdns::Config;
|
use libp2p::mdns::Config;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ impl TryFrom<PartialStorageConfig> for StorageConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg_attr(feature="desktop", derive(Args))]
|
#[cfg_attr(feature="desktop", derive(Args))]
|
||||||
|
#[cfg_attr(feature="macros", derive(Emptiable))]
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct PartialStorageConfig {
|
pub struct PartialStorageConfig {
|
||||||
#[cfg_attr(feature="desktop", arg(long))]
|
#[cfg_attr(feature="desktop", arg(long))]
|
||||||
|
@ -65,27 +66,4 @@ impl From<StorageConfig> for PartialStorageConfig {
|
||||||
cache_directory: Some(config.cache_directory),
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
#[cfg(feature="macros")]
|
||||||
|
pub use lazy_supplements_macros::Emptiable;
|
||||||
|
|
||||||
pub trait Emptiable{
|
pub trait Emptiable{
|
||||||
fn empty() -> Self;
|
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 async_convert;
|
||||||
pub mod emptiable;
|
pub mod emptiable;
|
||||||
|
pub mod mergeable;
|
||||||
pub mod runnable;
|
pub mod runnable;
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#[cfg(feature="macros")]
|
||||||
|
pub use lazy_supplements_macros::Runnable;
|
||||||
|
|
||||||
pub trait Runnable {
|
pub trait Runnable {
|
||||||
async fn run(self);
|
async fn run(self);
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
use crate::utils::runnable::Runnable;
|
||||||
|
|
||||||
use crate::cli::{ConfigArgs, RunnableCommand};
|
use crate::cli::ConfigArgs;
|
||||||
|
|
||||||
use crate::cli::PeerArgs;
|
use crate::cli::PeerArgs;
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ pub struct DeviceAddCommandArgs {
|
||||||
config: ConfigArgs
|
config: ConfigArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunnableCommand for DeviceAddCommandArgs {
|
impl Runnable for DeviceAddCommandArgs {
|
||||||
async fn run(self) {
|
async fn run(self) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
use crate::utils::runnable::Runnable;
|
||||||
use crate::cli::{ConfigArgs, RunnableCommand};
|
use crate::cli::{ConfigArgs, RunnableCommand};
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
|
@ -8,7 +8,7 @@ pub struct DeviceListCommandArgs{
|
||||||
config: ConfigArgs
|
config: ConfigArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunnableCommand for DeviceListCommandArgs {
|
impl Runnable for DeviceListCommandArgs {
|
||||||
async fn run(self) {
|
async fn run(self) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ mod remove;
|
||||||
mod scan;
|
mod scan;
|
||||||
|
|
||||||
pub use add::DeviceAddCommandArgs;
|
pub use add::DeviceAddCommandArgs;
|
||||||
|
use crate::utils::runnable::Runnable;
|
||||||
use libp2p::{Multiaddr, PeerId};
|
use libp2p::{Multiaddr, PeerId};
|
||||||
pub use list::DeviceListCommandArgs;
|
pub use list::DeviceListCommandArgs;
|
||||||
pub use ping::DevicePingCommandArgs;
|
pub use ping::DevicePingCommandArgs;
|
||||||
|
@ -20,13 +21,14 @@ use crate::{cli::ServerArgs, error::Error};
|
||||||
use super::ConfigArgs;
|
use super::ConfigArgs;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args, Runnable)]
|
||||||
pub struct DeviceCommandArgs {
|
pub struct DeviceCommandArgs {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
#[runnable]
|
||||||
pub command: DeviceSubcommand
|
pub command: DeviceSubcommand
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand, Runnable)]
|
||||||
pub enum DeviceSubcommand {
|
pub enum DeviceSubcommand {
|
||||||
Add(DeviceAddCommandArgs),
|
Add(DeviceAddCommandArgs),
|
||||||
List(DeviceListCommandArgs),
|
List(DeviceListCommandArgs),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
use crate::utils::runnable::Runnable;
|
||||||
use crate::cli::{ConfigArgs, PeerArgs, RunnableCommand};
|
use crate::cli::{ConfigArgs, PeerArgs};
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct DevicePingCommandArgs{
|
pub struct DevicePingCommandArgs{
|
||||||
|
@ -10,7 +10,7 @@ pub struct DevicePingCommandArgs{
|
||||||
config: ConfigArgs
|
config: ConfigArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunnableCommand for DevicePingCommandArgs {
|
impl Runnable for DevicePingCommandArgs {
|
||||||
async fn run(self) {
|
async fn run(self) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
use crate::utils::runnable::Runnable;
|
||||||
use crate::cli::{ConfigArgs, DeviceArgs, RunnableCommand};
|
use crate::cli::{ConfigArgs, DeviceArgs, RunnableCommand};
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
|
@ -10,7 +10,7 @@ pub struct DeviceRemoveCommandArgs{
|
||||||
config: ConfigArgs
|
config: ConfigArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunnableCommand for DeviceRemoveCommandArgs {
|
impl Runnable for DeviceRemoveCommandArgs {
|
||||||
async fn run(self) {
|
async fn run(self) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
use crate::utils::runnable::Runnable;
|
||||||
use crate::cli::{ConfigArgs, RunnableCommand};
|
use crate::cli::{ConfigArgs, RunnableCommand};
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
|
@ -8,7 +8,7 @@ pub struct DeviceScanCommandArgs{
|
||||||
config: ConfigArgs
|
config: ConfigArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunnableCommand for DeviceScanCommandArgs {
|
impl Runnable for DeviceScanCommandArgs {
|
||||||
async fn run(self) {
|
async fn run(self) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ pub mod cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod global;
|
pub mod global;
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
|
pub mod utils;
|
||||||
pub use lazy_supplements_core::{
|
pub use lazy_supplements_core::{
|
||||||
cache,
|
cache,
|
||||||
data,
|
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)]
|
#[proc_macro_derive(Emptiable)]
|
||||||
pub fn emptiable(input: TokenStream) -> TokenStream {
|
pub fn emptiable(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
@ -166,11 +165,32 @@ pub fn emptiable(input: TokenStream) -> TokenStream {
|
||||||
}
|
}
|
||||||
}.into()
|
}.into()
|
||||||
}
|
}
|
||||||
Data::Enum(ref fields) => {
|
_ => panic!("struct or expected, but got other type.")
|
||||||
todo!()
|
|
||||||
|
|
||||||
},
|
}
|
||||||
_ => 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