plantuml_server_client_rs/config/
file.rs1use crate::config::ConfigTrait;
2use crate::{Format, MetadataVersion, Method};
3use anyhow::Context as _;
4use dirs::home_dir;
5use serde::Deserialize;
6use std::path::PathBuf;
7use tokio::fs::read_to_string;
8
9const PATHS: [&str; 2] = [".pscr.conf", ".config/.pscr.conf"];
11
12#[doc = include_str!("../../tests/assets/config/.pscr.conf")]
18#[derive(Deserialize, Clone, Debug)]
20#[serde(rename_all = "kebab-case")]
21pub struct FileConfig {
22 pscr: Config,
23}
24
25#[derive(Deserialize, Clone, Debug)]
26#[serde(rename_all = "kebab-case")]
27struct Config {
28 #[serde(default, deserialize_with = "de::deserialize_option_from_str")]
29 method: Option<Method>,
30
31 #[serde(default)]
32 url_prefix: Option<String>,
33
34 #[serde(default, deserialize_with = "de::deserialize_option_from_str")]
35 format: Option<Format>,
36
37 #[serde(default)]
38 combined: Option<bool>,
39
40 #[serde(default)]
41 metadata_version: Option<MetadataVersion>,
42}
43
44impl FileConfig {
45 pub async fn read_from_file(filepath: &PathBuf) -> anyhow::Result<Self> {
64 let data = read_to_string(filepath)
65 .await
66 .map_err(|e| {
67 tracing::warn!("failed to read '{filepath:?}': {e:?}");
68 e
69 })
70 .with_context(|| format!("failed to read '{filepath:?}'"))?;
71
72 toml::from_str(&data)
73 .map_err(|e| {
74 tracing::warn!("failed to parse '{filepath:?}': {e:?}");
75 e
76 })
77 .with_context(|| format!("failed to parse '{filepath:?}'"))
78 }
79
80 pub async fn read_from_default_path() -> Option<Self> {
90 let paths = Self::default_path_list();
91
92 for filepath in paths.into_iter() {
93 match Self::read_from_file(&filepath).await {
94 Ok(config) => {
95 return Some(config);
96 }
97 Err(_) => {
98 continue;
99 }
100 }
101 }
102
103 None
104 }
105
106 fn default_path_list() -> Vec<PathBuf> {
107 let prefixs: Vec<_> = [PathBuf::from(".")].into_iter().chain(home_dir()).collect();
108 let mut ret = vec![];
109
110 for prefix in prefixs.into_iter() {
111 for basepath in PATHS.iter() {
112 let mut filepath = prefix.clone();
113 filepath.push(basepath);
114 ret.push(filepath);
115 }
116 }
117
118 ret
119 }
120}
121
122impl ConfigTrait for FileConfig {
123 fn method(&self) -> Option<Method> {
125 self.pscr.method.clone()
126 }
127
128 fn url_prefix(&self) -> Option<String> {
130 self.pscr.url_prefix.clone()
131 }
132
133 fn format(&self) -> Option<Format> {
135 self.pscr.format.clone()
136 }
137
138 fn combined(&self) -> Option<bool> {
140 self.pscr.combined
141 }
142
143 fn metadata_version(&self) -> Option<MetadataVersion> {
145 self.pscr.metadata_version
146 }
147}
148
149mod de {
150 use serde::de::{Deserialize, Deserializer, Error as DeError};
151 use std::str::FromStr;
152
153 pub fn deserialize_option_from_str<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
154 where
155 D: Deserializer<'de>,
156 T: FromStr,
157 <T as FromStr>::Err: std::fmt::Display,
158 {
159 Option::<String>::deserialize(deserializer)?
160 .map(|s| T::from_str(&s).map_err(DeError::custom))
161 .transpose()
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 use itertools::Itertools;
170
171 #[tokio::test]
172 async fn test_de() -> anyhow::Result<()> {
173 let method_list = [
174 ("", None),
175 (r#"method = "post""#, Some(Method::Post)),
176 (r#"method = "get""#, Some(Method::Get)),
177 ];
178
179 let url_prefix_list = [
180 ("", None),
181 (
182 r#"url-prefix = "http://localhost:8080/""#,
183 Some("http://localhost:8080/"),
184 ),
185 ];
186
187 let format_list = [
188 ("", None),
189 (r#"format = "svg""#, Some(Format::Svg)),
190 (r#"format = "png""#, Some(Format::Png)),
191 (r#"format = "txt""#, Some(Format::Ascii)),
192 ];
193
194 let combined_list = [
195 ("", None),
196 (r#"combined = false"#, Some(false)),
197 (r#"combined = true"#, Some(true)),
198 ];
199
200 let metadata_version_list = [
201 ("", None),
202 (r#"metadata-version = "v2""#, Some(MetadataVersion::V2)),
203 ];
204
205 for ((((method, url_prefix), format), combined), metadata_version) in method_list
206 .iter()
207 .cartesian_product(url_prefix_list)
208 .cartesian_product(format_list)
209 .cartesian_product(combined_list)
210 .cartesian_product(metadata_version_list)
211 {
212 let (method_line, expected_method) = method;
213 let (url_prefix_line, expected_url_prefix) = url_prefix;
214 let (format_line, expected_format) = format;
215 let (combined_line, expected_combined) = combined;
216 let (metadata_version_line, expected_metadata_format_version) = metadata_version;
217
218 let testdata = format!(
219 r#"
220 [pscr]
221 {method_line}
222 {url_prefix_line}
223 {format_line}
224 {combined_line}
225 {metadata_version_line}
226 "#
227 );
228
229 println!("testdata: {testdata}");
230 let config: FileConfig = toml::from_str(&testdata)?;
231
232 assert_eq!(expected_method, &config.method());
233 assert_eq!(expected_url_prefix, config.url_prefix().as_deref());
234 assert_eq!(expected_format, config.format());
235 assert_eq!(expected_combined, config.combined());
236 assert_eq!(expected_metadata_format_version, config.metadata_version());
237 }
238
239 Ok(())
240 }
241
242 #[tokio::test]
243 async fn test_default_path_list() -> anyhow::Result<()> {
244 let paths = FileConfig::default_path_list();
245
246 assert_eq!(paths[0], PathBuf::from("./.pscr.conf"));
247 assert_eq!(paths[1], PathBuf::from("./.config/.pscr.conf"));
248
249 if let Some(homedir) = home_dir() {
250 let home = homedir.to_str().unwrap();
251
252 let path: PathBuf = [home, ".pscr.conf"].iter().collect();
253 assert_eq!(paths[2], path);
254
255 let path: PathBuf = [home, ".config", ".pscr.conf"].iter().collect();
256 assert_eq!(paths[3], path);
257 }
258
259 Ok(())
260 }
261}