currency_conversion_cli/
config.rs

1use std::{io::Stdin, path::PathBuf};
2
3use anyhow::{bail, Result};
4use serde::{Deserialize, Serialize};
5
6/// Config file structure
7#[derive(Debug, Deserialize, Serialize, Eq, PartialEq)]
8pub struct Config {
9    /// API token
10    pub api_key: String,
11    /// base currency
12    pub base: String,
13    /// endpoint url to get supported symbols (param : {api_key})
14    pub symbols_endpoint_url: String,
15    /// endpoint url to get conversion rates (param : {api_key}, {base})
16    pub latest_endpoint_url: String,
17    /// file path where supported symbols are stored
18    pub symbols_file_path: String,
19    // file path where conversion rates are stored
20    pub conversion_rates_file_path: String,
21}
22
23#[cfg(not(tarpaulin_include))]
24impl Default for Config {
25    fn default() -> Self {
26        // If error to find home_dir => panic
27        let homedir = home::home_dir().unwrap();
28
29        let mut symbols_file_path = PathBuf::new();
30        symbols_file_path.push(&homedir);
31        symbols_file_path.push(".currency-conversion-cli/symbols.tsv");
32
33        let mut conversion_rates_file_path = PathBuf::new();
34        conversion_rates_file_path.push(&homedir);
35        conversion_rates_file_path.push(".currency-conversion-cli/conversion_rates.tsv");
36
37        Config {
38            api_key: "#INSERT_API_KEY_HERE#".to_string(),
39            base: "EUR".to_string(),
40            // Not sure of this one : it can cause problem in case of path with non utf-8
41            // characters
42            // TODO : refactor to replace String to Pathbuf
43            symbols_file_path: symbols_file_path.to_string_lossy().into_owned(),
44            conversion_rates_file_path: conversion_rates_file_path.to_string_lossy().into_owned(),
45            latest_endpoint_url:
46                "http://api.exchangeratesapi.io/v1/latest?access_key={api_key}&base={base}"
47                    .to_string(),
48            symbols_endpoint_url: "http://api.exchangeratesapi.io/v1/symbols?access_key={api_key}"
49                .to_string(),
50        }
51    }
52}
53
54#[cfg(not(tarpaulin_include))]
55impl Config {
56    pub fn prompt_config(&self) -> Result<Config> {
57        let mut res = Config::default();
58        let stdin = std::io::stdin();
59        let mut buffer = String::new();
60        println!("Initialization of config file");
61
62        // api key
63        println!(
64            "api key (required exchange rates api key)(current : {}) : ",
65            self.api_key
66        );
67        stdin.read_line(&mut buffer)?;
68        if !buffer.trim().is_empty() {
69            res.api_key.clone_from(&buffer.trim().to_string());
70        } else if self.api_key != "#INSERT_API_KEY_HERE#" {
71            res.api_key.clone_from(&self.api_key);
72        } else {
73            bail!("API key must be provided !")
74        }
75
76        // base
77        res.base
78            .clone_from(&prompt_string(&stdin, "base currency", &self.base)?);
79        // symbols file path
80        res.symbols_file_path.clone_from(&prompt_string(
81            &stdin,
82            "currency symbols file path",
83            &self.symbols_file_path,
84        )?);
85        // symbols endpoint
86        res.symbols_endpoint_url.clone_from(&prompt_string(
87            &stdin,
88            "currency symbols endpoint URL",
89            &self.symbols_endpoint_url,
90        )?);
91        // converison rates file path
92        res.conversion_rates_file_path.clone_from(&prompt_string(
93            &stdin,
94            "conversion rates file path",
95            &self.conversion_rates_file_path,
96        )?);
97        // conversion rates endpoint
98        res.latest_endpoint_url.clone_from(&prompt_string(
99            &stdin,
100            "conversion rates endpoint URL",
101            &self.latest_endpoint_url,
102        )?);
103
104        Ok(res)
105    }
106}
107
108#[cfg(not(tarpaulin_include))]
109fn prompt_string(stdin: &Stdin, text: &str, current_value: &String) -> Result<String> {
110    println!("{text} (current : {current_value}) : ");
111    let mut buffer = String::new();
112    stdin.read_line(&mut buffer)?;
113    if buffer.trim().is_empty() {
114        return Ok(current_value.clone());
115    }
116
117    Ok(buffer.trim().to_string())
118}