Skip to main content

ghost_crab_common/
config.rs

1use dotenvy::dotenv;
2use serde::Deserialize;
3use serde_json::Error as SerdeError;
4use std::path::PathBuf;
5use std::{collections::HashMap, io::Error as IoError};
6use std::{env, fs};
7
8#[derive(Clone, Copy, Deserialize, Debug)]
9#[serde(rename_all = "lowercase")]
10pub enum ExecutionMode {
11    Parallel,
12    Serial,
13}
14
15#[derive(Debug, Deserialize, Clone)]
16#[serde(rename_all = "camelCase")]
17pub struct Template {
18    pub abi: String,
19    pub network: String,
20    pub execution_mode: Option<ExecutionMode>,
21}
22
23#[derive(Debug, Deserialize, Clone)]
24#[serde(rename_all = "camelCase")]
25pub struct DataSource {
26    pub abi: String,
27    pub address: String,
28    pub start_block: u64,
29    pub network: String,
30    pub execution_mode: Option<ExecutionMode>,
31}
32
33#[derive(Debug, Deserialize, Clone)]
34#[serde(rename_all = "camelCase")]
35pub struct BlockHandlerConfig {
36    pub start_block: u64,
37    pub network: String,
38    pub execution_mode: Option<ExecutionMode>,
39    pub step: u64,
40}
41
42#[derive(Debug, Deserialize, Clone)]
43#[serde(rename_all = "camelCase")]
44pub struct NetworkConfig {
45    pub rpc_url: String,
46    pub requests_per_second: u64,
47}
48
49#[derive(Debug, Deserialize, Clone)]
50#[serde(rename_all = "camelCase")]
51pub struct Config {
52    pub data_sources: HashMap<String, DataSource>,
53    pub templates: HashMap<String, Template>,
54    pub networks: HashMap<String, NetworkConfig>,
55    pub block_handlers: HashMap<String, BlockHandlerConfig>,
56}
57
58#[derive(Debug)]
59pub enum ConfigError {
60    FileNotFound(IoError),
61    CurrentDirNotFound(IoError),
62    InvalidConfig(SerdeError),
63    EnvVarNotFound(String),
64}
65
66impl std::fmt::Display for ConfigError {
67    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
68        match self {
69            ConfigError::FileNotFound(error) => {
70                write!(formatter, "Config file not found: {}", error)
71            }
72            ConfigError::CurrentDirNotFound(error) => {
73                write!(formatter, "Current directory not found: {}", error)
74            }
75            ConfigError::InvalidConfig(error) => {
76                write!(formatter, "Invalid config format: {}", error)
77            }
78            ConfigError::EnvVarNotFound(var) => {
79                write!(formatter, "Environment variable not found: {}", var)
80            }
81        }
82    }
83}
84
85impl std::error::Error for ConfigError {}
86
87pub fn load() -> Result<Config, ConfigError> {
88    dotenv().ok();
89
90    let config_path = get_config_path()?;
91    let config_string = read_config_file(&config_path)?;
92    let mut config: Config = parse_config(&config_string)?;
93    replace_env_vars(&mut config)?;
94
95    Ok(config)
96}
97
98fn get_config_path() -> Result<PathBuf, ConfigError> {
99    let current_dir = env::current_dir().map_err(|error| ConfigError::CurrentDirNotFound(error))?;
100    Ok(current_dir.join("config.json"))
101}
102
103fn read_config_file(path: &PathBuf) -> Result<String, ConfigError> {
104    fs::read_to_string(path).map_err(|error| ConfigError::FileNotFound(error))
105}
106
107fn parse_config(config_string: &str) -> Result<Config, ConfigError> {
108    serde_json::from_str(config_string).map_err(|error| ConfigError::InvalidConfig(error))
109}
110
111fn replace_env_vars(config: &mut Config) -> Result<(), ConfigError> {
112    for (_key, value) in &mut config.networks {
113        if value.rpc_url.starts_with('$') {
114            (*value).rpc_url = env::var(&value.rpc_url[1..])
115                .map_err(|_| ConfigError::EnvVarNotFound(value.rpc_url[1..].to_string()))?;
116        }
117    }
118
119    Ok(())
120}