mevlog 0.9.0

EVM transactions monitoring and querying CLI/TUI powered by Revm
Documentation
use std::{collections::HashMap, fs, path::PathBuf};

use eyre::Result;
use serde::{Deserialize, Serialize};

use crate::misc::shared_init::config_path;

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Config {
    #[serde(default)]
    chains: HashMap<String, ChainConfig>,
}

impl Config {
    pub fn chains(&self) -> impl Iterator<Item = (u64, &ChainConfig)> {
        self.chains
            .iter()
            .filter_map(|(k, v)| k.parse::<u64>().ok().map(|id| (id, v)))
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainConfig {
    pub rpc_url: String,
}

impl Config {
    pub fn config_file_path() -> PathBuf {
        config_path().join("config.toml")
    }

    pub fn load() -> Result<Self> {
        let path = Self::config_file_path();
        if !path.exists() {
            return Ok(Self::default());
        }
        let content = fs::read_to_string(&path)?;
        Ok(toml::from_str(&content)?)
    }

    pub fn init_if_missing() -> Result<()> {
        let path = Self::config_file_path();
        if !path.exists() {
            fs::create_dir_all(config_path())?;
            fs::write(&path, Self::default_config_template())?;
        }
        Ok(())
    }

    fn default_config_template() -> &'static str {
        r#"# mevlog configuration file
#
# Configure custom RPC endpoints for each chain by chain ID.
# Uncomment and modify the examples below as needed.
#
# [chains.1]
# rpc_url = "https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"
#
# [chains.42161]
# rpc_url = "https://arb-mainnet.g.alchemy.com/v2/YOUR_API_KEY"
"#
    }

    pub fn get_chain(&self, chain_id: u64) -> Option<&ChainConfig> {
        let key = chain_id.to_string();
        self.chains.get(&key)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_unquoted() {
        let content = r#"
[chains.1]
rpc_url = "https://example.com"
"#;
        let config: Config = toml::from_str(content).unwrap();
        assert!(config.get_chain(1).is_some());
    }

    #[test]
    fn test_parse_quoted() {
        let content = r#"
[chains."1"]
rpc_url = "https://example.com"
"#;
        let config: Config = toml::from_str(content).unwrap();
        assert!(config.get_chain(1).is_some());
    }
}