config_manager/
utils.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2022 JSRPC “Kryptonite”
3
4use std::{
5    collections::HashMap,
6    hash::{Hash, Hasher},
7};
8
9use crate::{ConfigOption, Source};
10
11pub(super) fn parse_subcommand<T>(
12    args: impl Iterator<Item = String>,
13    am: &clap::ArgMatches,
14) -> Result<Option<T>, crate::Error>
15where
16    T: clap::Subcommand,
17{
18    let subname = match am.subcommand_name() {
19        None => return Ok(None),
20        Some(name) => name,
21    };
22    let vars = args.collect::<Vec<_>>();
23    let (_, subcommand) = vars.split_at(
24        vars.iter()
25            .position(|arg| arg.to_lowercase() == subname)
26            .ok_or_else(|| {
27                crate::Error::ExternalError("not found subcommand name in std::env::args()".into())
28            })?,
29    );
30    let subcommand = [&["".to_string()], subcommand].concat();
31
32    #[derive(clap::Parser)]
33    struct Supporting<S: clap::Subcommand> {
34        #[clap(subcommand)]
35        inner: S,
36    }
37
38    <Supporting<T> as clap::Parser>::try_parse_from(subcommand)
39        .map(|supporting| Some(supporting.inner))
40        .map_err(|err| {
41            crate::Error::ExternalError(format!("wrong parsing in parse_subcommand: {err}"))
42        })
43}
44
45pub(super) fn find_field_in_table(
46    config: &HashMap<String, config::Value>,
47    table: Option<String>,
48    field_name: String,
49) -> Result<Option<String>, crate::Error> {
50    let mut field_segs = deconstruct_table_path(&field_name).collect::<Vec<_>>();
51    let field = field_segs.pop().ok_or_else(|| {
52        crate::Error::FailedParse(format!("Empty path segments of the field: {field_name}"))
53    })?;
54
55    let sub_config = match table {
56        None => match find_sub_table(config, field_segs.into_iter())? {
57            None => return Ok(None),
58            Some(sub_config) => sub_config,
59        },
60        Some(table) => match find_sub_table(
61            config,
62            deconstruct_table_path(&table).chain(field_segs.into_iter()),
63        )? {
64            None => return Ok(None),
65            Some(sub_config) => sub_config,
66        },
67    };
68
69    if let Some(value) = sub_config.get(&field) {
70        from_config_to_string(value.clone()).map(Some)
71    } else {
72        Ok(None)
73    }
74}
75
76fn find_sub_table(
77    parent_config: &HashMap<String, config::Value>,
78    mut table: impl Iterator<Item = String>,
79) -> Result<Option<&HashMap<String, config::Value>>, crate::Error> {
80    let first_segment = match table.next() {
81        None => return Ok(Some(parent_config)),
82        Some(seg) => seg,
83    };
84
85    match &parent_config.get(&first_segment) {
86        None => Ok(None),
87        Some(sub_table) => {
88            if let config::ValueKind::Table(sub_table) = &sub_table.kind {
89                find_sub_table(sub_table, table)
90            } else {
91                Err(crate::Error::FailedParse(format!(
92                    "Field {first_segment} is found in configuration files but it is not a table"
93                )))
94            }
95        }
96    }
97}
98
99fn deconstruct_table_path(table: &str) -> impl Iterator<Item = String> + '_ {
100    table.split(|dot| dot == '.').map(ToString::to_string)
101}
102
103fn from_config_to_string(initial: config::Value) -> Result<String, super::Error> {
104    fn from_config_to_serde_json(
105        initial: config::ValueKind,
106    ) -> Result<serde_json::Value, super::Error> {
107        match initial {
108            config::ValueKind::Nil => Ok(serde_json::Value::Null),
109            config::ValueKind::Boolean(b) => Ok(serde_json::Value::Bool(b)),
110            config::ValueKind::I64(i) => Ok(serde_json::Value::Number(
111                serde_json::value::Number::from(i),
112            )),
113            config::ValueKind::U64(u) => Ok(serde_json::Value::Number(
114                serde_json::value::Number::from(u),
115            )),
116            config::ValueKind::Float(f) => Ok(serde_json::Value::Number(
117                serde_json::value::Number::from_f64(f).ok_or_else(|| {
118                    super::Error::FailedParse(format!(
119                        "failed to convert to serde_json Number from from f64 {}",
120                        f
121                    ))
122                })?,
123            )),
124            config::ValueKind::String(s) => Ok(serde_json::Value::String(s)),
125            config::ValueKind::Table(tbl) => {
126                Ok(serde_json::Value::Object(serde_json::Map::from_iter({
127                    let mut res = vec![];
128                    for (k, v) in tbl {
129                        res.push((k, from_config_to_serde_json(v.kind)?));
130                    }
131                    res.into_iter()
132                })))
133            }
134            config::ValueKind::Array(arr) => Ok(serde_json::Value::Array({
135                let mut res = vec![];
136                for v in arr {
137                    res.push(from_config_to_serde_json(v.kind)?);
138                }
139                res
140            })),
141            config::ValueKind::I128(num) => Ok(serde_json::Value::Number(
142                ::std::str::FromStr::from_str(&num.to_string()).map_err(|_| {
143                    super::Error::FailedParse(format!(
144                        "can't convert to serde_json::value::Number from I128, value: {})",
145                        num
146                    ))
147                })?,
148            )),
149            config::ValueKind::U128(num) => Ok(serde_json::Value::Number(
150                ::std::str::FromStr::from_str(&num.to_string()).map_err(|_| {
151                    super::Error::FailedParse(format!(
152                        "can't convert to serde_json::value::Number from U128, value: {})",
153                        num
154                    ))
155                })?,
156            )),
157        }
158    }
159    match initial.kind {
160        config::ValueKind::I128(num) => Ok(num.to_string()),
161        config::ValueKind::U128(num) => Ok(num.to_string()),
162        value => Ok(from_config_to_serde_json(value).map(|value| value.to_string())?),
163    }
164}
165
166impl PartialEq for ConfigOption {
167    fn eq(&self, other: &Self) -> bool {
168        matches!(
169            (self, other),
170            (ConfigOption::EnvPrefix(_), ConfigOption::EnvPrefix(_))
171                | (
172                    ConfigOption::ExplicitSource(Source::Clap(_)),
173                    ConfigOption::ExplicitSource(Source::Clap(_)),
174                )
175                | (
176                    ConfigOption::ExplicitSource(Source::ConfigFiles(_)),
177                    ConfigOption::ExplicitSource(Source::ConfigFiles(_)),
178                )
179                | (
180                    ConfigOption::ExplicitSource(Source::Env(_)),
181                    ConfigOption::ExplicitSource(Source::Env(_)),
182                )
183        )
184    }
185}
186impl Eq for ConfigOption {}
187
188impl Hash for ConfigOption {
189    fn hash<H: Hasher>(&self, state: &mut H) {
190        match self {
191            ConfigOption::EnvPrefix(_) => state.write_u8(1),
192            ConfigOption::ExplicitSource(Source::Clap(_)) => state.write_u8(2),
193            ConfigOption::ExplicitSource(Source::ConfigFiles(_)) => state.write_u8(3),
194            ConfigOption::ExplicitSource(Source::Env(_)) => state.write_u8(4),
195        }
196    }
197}