caretta-sync-core 0.0.2

A local-first application framework for lazy person
Documentation
pub mod error;
mod iroh;
mod rpc;
mod storage;

use crate::utils::{emptiable::Emptiable, mergeable::Mergeable};
pub use error::ConfigError;
use serde::{Deserialize, Serialize};
use std::{
    default::Default,
    fs::File,
    io::{Read, Write},
    path::Path,
};

pub use iroh::{IrohConfig, PartialIrohConfig};
pub use rpc::*;
pub use storage::{PartialStorageConfig, StorageConfig};
use tokio::io::{AsyncReadExt, AsyncWriteExt};

#[cfg(feature = "cli")]
use clap::Args;

#[derive(Clone, Debug)]
pub struct Config {
    pub iroh: IrohConfig,
    pub storage: StorageConfig,
    pub rpc: RpcConfig,
}

impl AsRef<StorageConfig> for Config {
    fn as_ref(&self) -> &StorageConfig {
        &self.storage
    }
}

impl AsRef<IrohConfig> for Config {
    fn as_ref(&self) -> &IrohConfig {
        &self.iroh
    }
}

impl AsRef<RpcConfig> for Config {
    fn as_ref(&self) -> &RpcConfig {
        &self.rpc
    }
}

impl TryFrom<PartialConfig> for Config {
    type Error = crate::error::Error;
    fn try_from(value: PartialConfig) -> Result<Self, Self::Error> {
        Ok(Self {
            rpc: value
                .rpc
                .ok_or(crate::error::Error::MissingConfig("rpc"))?
                .try_into()?,
            iroh: value
                .iroh
                .ok_or(crate::error::Error::MissingConfig("p2p"))?
                .try_into()?,
            storage: value
                .storage
                .ok_or(crate::error::Error::MissingConfig("storage"))?
                .try_into()?,
        })
    }
}

#[cfg_attr(feature = "cli", derive(Args))]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PartialConfig {
    #[cfg_attr(feature = "cli", command(flatten))]
    pub iroh: Option<PartialIrohConfig>,
    #[cfg_attr(feature = "cli", command(flatten))]
    pub storage: Option<PartialStorageConfig>,
    #[cfg_attr(feature = "cli", command(flatten))]
    pub rpc: Option<PartialRpcConfig>,
}

impl Default for PartialConfig {
    fn default() -> Self {
        Self::new()
    }
}

impl PartialConfig {
    pub fn new() -> Self {
        Self {
            iroh: Some(PartialIrohConfig::empty().with_new_secret_key()),
            storage: Some(PartialStorageConfig::empty()),
            rpc: Some(PartialRpcConfig::empty()),
        }
    }
    pub fn from_toml(s: &str) -> Result<Self, toml::de::Error> {
        toml::from_str(s)
    }
    pub fn into_toml(&self) -> Result<String, toml::ser::Error> {
        toml::to_string(self)
    }
    pub fn read_or_create<T>(path: T) -> Result<Self, ConfigError>
    where
        T: AsRef<Path>,
    {
        if !path.as_ref().exists() {
            Self::new().write_to(&path)?;
        }
        Self::read_from(&path)
    }
    pub fn read_from<T>(path: T) -> Result<Self, ConfigError>
    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)?;
        }
        let mut file = File::open(path.as_ref())?;
        let mut content = String::new();
        file.read_to_string(&mut content)?;
        let config: Self = toml::from_str(&content)?;
        Ok(config)
    }
    pub fn write_to<T>(&self, path: T) -> Result<(), ConfigError>
    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)?;
        }
        let mut file = File::create(&path)?;
        file.write_all(toml::to_string(self)?.as_bytes())?;
        Ok(())
    }
    pub fn default(app_name: &'static str) -> Self {
        Self {
            iroh: Some(PartialIrohConfig::default()),
            rpc: Some(PartialRpcConfig::default(app_name)),
            storage: Some(PartialStorageConfig::default(app_name)),
        }
    }
}

impl From<Config> for PartialConfig {
    fn from(value: Config) -> Self {
        Self {
            iroh: Some(value.iroh.into()),
            storage: Some(value.storage.into()),
            rpc: Some(value.rpc.into()),
        }
    }
}

impl Emptiable for PartialConfig {
    fn empty() -> Self {
        Self {
            iroh: None,
            storage: None,
            rpc: None,
        }
    }

    fn is_empty(&self) -> bool {
        self.iroh.is_empty() && self.rpc.is_empty() && self.storage.is_empty()
    }
}

impl Mergeable for PartialConfig {
    fn merge(&mut self, other: Self) {
        self.iroh.merge(other.iroh);
        self.rpc.merge(other.rpc);
        self.storage.merge(other.storage);
    }
}