Skip to main content

ohlcv_ctl/
config.rs

1//! Configuration for ohlcv-ctl.
2
3use 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
11/// Name of the default configuration file.
12pub const CONFIG_FILE: &str = concat!(env!("CARGO_PKG_NAME"), ".toml",);
13
14/// Default paths to search for the configuration file if not specified by the
15/// user either through a command-line argument or environment variable. The
16/// paths are appended with [`CONFIG_FILE`] to form the full path to the
17/// configuration file. Paths are searched in order, and the first file found is
18/// used.
19pub const CONFIG_PATHS: [&str; 2] = [".", "/etc/ohlcv"];
20
21const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
22
23/// Map of exchange names to the coin's symbol on that exchange.
24pub type ExchangeMap = HashMap<Exchange, String>;
25
26/// Configuration for a coin.
27#[derive(Debug, Deserialize)]
28#[allow(clippy::module_name_repetitions, dead_code)]
29pub struct CoinConfig {
30    symbol: String,
31    name: String,
32    currency: Currency,
33    /// Map of exchange names to the coin's symbol on that exchange.
34    pub exchanges: ExchangeMap,
35}
36
37impl CoinConfig {
38    /// Convert the configuration into a [`Coin`] instance.
39    #[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/// Top-level configuration structure.
46#[derive(Debug, Deserialize)]
47pub struct Config {
48    user_agent: Option<Box<str>>,
49    /// Database connection information.
50    pub database: DbType,
51    /// List of coins to fetch.
52    pub coins: Vec<CoinConfig>,
53}
54
55impl Config {
56    /// Load the configuration from the specified file.
57    ///
58    /// # Errors
59    ///
60    /// This function returns an error if the file cannot be read or if the
61    /// configuration is not valid TOML defined by the [`Config`] struct.
62    #[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    /// Get the user agent string to use for HTTP requests.
80    #[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}