1use 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('.').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}