1use std::{collections::HashMap, fmt, path::Path};
4
5use ohlcv::{database::DbType, Coin, Currency, Exchange};
6use serde::Deserialize;
7use tracing::{info, instrument};
8
9use crate::Error;
10
11pub const CONFIG_FILE: &str = concat!(env!("CARGO_PKG_NAME"), ".toml",);
13
14pub const CONFIG_PATHS: [&str; 2] = [".", "/etc/ohlcv"];
20
21const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
22
23pub type ExchangeMap = HashMap<Exchange, String>;
25
26#[derive(Debug, Deserialize)]
28#[allow(clippy::module_name_repetitions, dead_code)]
29pub struct CoinConfig {
30 symbol: String,
31 name: String,
32 currency: Currency,
33 pub exchanges: ExchangeMap,
35}
36
37impl CoinConfig {
38 #[must_use]
40 pub fn as_coin(&self) -> ohlcv::Coin {
41 Coin::new(self.symbol.clone(), self.name.clone(), self.currency)
42 }
43}
44
45#[derive(Debug, Deserialize)]
47pub struct Config {
48 user_agent: Option<Box<str>>,
49 pub database: DbType,
51 pub coins: Vec<CoinConfig>,
53}
54
55impl Config {
56 #[instrument]
63 pub fn load(path: Option<impl AsRef<Path> + fmt::Debug>) -> Result<Self, Error> {
64 let path = path
65 .map(|p| p.as_ref().to_path_buf())
66 .or_else(|| {
67 CONFIG_PATHS
68 .iter()
69 .map(|p| Path::new(p).join(CONFIG_FILE))
70 .find(|p| p.exists())
71 })
72 .ok_or_else(|| Error::ConfigFile)?;
73 info!("Loading configuration from {:?}", path);
74 let source = std::fs::read_to_string(path)?;
75
76 toml::from_str(&source).map_err(Error::ConfigFormat)
77 }
78
79 #[must_use]
81 #[inline]
82 #[instrument(skip(self))]
83 pub fn user_agent(&self) -> &str {
84 self.user_agent.as_deref().unwrap_or(USER_AGENT)
85 }
86}