ghost_crab_common/
config.rs1use 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}