diff --git a/ghost-crab-common/src/config.rs b/ghost-crab-common/src/config.rs index 53cc945..d771be1 100644 --- a/ghost-crab-common/src/config.rs +++ b/ghost-crab-common/src/config.rs @@ -1,5 +1,9 @@ +use dotenvy::dotenv; use serde::Deserialize; -use std::collections::HashMap; +use serde_json::Error as SerdeError; +use std::path::PathBuf; +use std::{collections::HashMap, io::Error as IoError}; +use std::{env, fs}; #[derive(Clone, Copy, Deserialize, Debug)] #[serde(rename_all = "lowercase")] @@ -43,3 +47,67 @@ pub struct Config { pub networks: HashMap, pub block_handlers: HashMap, } + +#[derive(Debug)] +pub enum ConfigError { + FileNotFound(IoError), + CurrentDirNotFound(IoError), + InvalidConfig(SerdeError), + EnvVarNotFound(String), +} + +impl std::fmt::Display for ConfigError { + fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + ConfigError::FileNotFound(error) => { + write!(formatter, "Config file not found: {}", error) + } + ConfigError::CurrentDirNotFound(error) => { + write!(formatter, "Current directory not found: {}", error) + } + ConfigError::InvalidConfig(error) => { + write!(formatter, "Invalid config format: {}", error) + } + ConfigError::EnvVarNotFound(var) => { + write!(formatter, "Environment variable not found: {}", var) + } + } + } +} + +impl std::error::Error for ConfigError {} + +pub fn load() -> Result { + dotenv().ok(); + + let config_path = get_config_path()?; + let config_string = read_config_file(&config_path)?; + let mut config: Config = parse_config(&config_string)?; + replace_env_vars(&mut config)?; + + Ok(config) +} + +fn get_config_path() -> Result { + let current_dir = env::current_dir().map_err(|error| ConfigError::CurrentDirNotFound(error))?; + Ok(current_dir.join("config.json")) +} + +fn read_config_file(path: &PathBuf) -> Result { + fs::read_to_string(path).map_err(|error| ConfigError::FileNotFound(error)) +} + +fn parse_config(config_string: &str) -> Result { + serde_json::from_str(config_string).map_err(|error| ConfigError::InvalidConfig(error)) +} + +fn replace_env_vars(config: &mut Config) -> Result<(), ConfigError> { + for (_key, value) in &mut config.networks { + if value.starts_with('$') { + *value = env::var(&value[1..]) + .map_err(|_| ConfigError::EnvVarNotFound(value[1..].to_string()))?; + } + } + + Ok(()) +} diff --git a/ghost-crab-macros/src/lib.rs b/ghost-crab-macros/src/lib.rs index ea0934b..2ed446d 100644 --- a/ghost-crab-macros/src/lib.rs +++ b/ghost-crab-macros/src/lib.rs @@ -1,9 +1,8 @@ extern crate proc_macro; -use ghost_crab_common::config::{Config, ExecutionMode}; +use ghost_crab_common::config::{self, ExecutionMode}; use proc_macro::TokenStream; use proc_macro2::{Ident, Literal}; use quote::{format_ident, quote}; -use std::fs; use syn::{parse_macro_input, ItemFn}; #[proc_macro_attribute] @@ -25,7 +24,7 @@ pub fn block_handler(metadata: TokenStream, input: TokenStream) -> TokenStream { panic!("The source is missing"); } - let config = get_config(); + let config = config::load().unwrap(); let source = config.block_handlers.get(name).expect("Source not found."); let rpc_url = config.networks.get(&source.network).expect("RPC url not found for network"); @@ -83,14 +82,6 @@ pub fn block_handler(metadata: TokenStream, input: TokenStream) -> TokenStream { }) } -fn get_config() -> Config { - let current_dir = std::env::current_dir().expect("Current directory not found"); - let config_json_path = current_dir.join("config.json"); - let content = fs::read_to_string(config_json_path).expect("Error reading config file"); - let config: Config = serde_json::from_str(&content).expect("Error parsing config file"); - return config; -} - fn get_source_and_event(metadata: TokenStream) -> (String, Ident) { let metadata_string = metadata.to_string(); let mut metadata_split = metadata_string.split('.'); @@ -137,7 +128,7 @@ fn get_context_identifier(parsed: ItemFn) -> Ident { fn create_handler(metadata: TokenStream, input: TokenStream, is_template: bool) -> TokenStream { let (name, event_name) = get_source_and_event(metadata); - let config = get_config(); + let config = config::load().unwrap(); let abi; let network;