1#![allow(clippy::module_name_repetitions)]
2use crate::error::Error;
3use crate::error::Result;
4use crate::install::expand_file;
5use crate::install::expand_path;
6use logger::LogLevel;
7use serde::{Deserialize, Serialize};
8use std::fs::read_to_string;
9use std::path::Path;
10use std::str::FromStr;
11use theme::Config as ThemeConfig;
12
13#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
15pub struct Config {
16 pub includes: Vec<String>,
18 pub files: Vec<String>,
20 #[serde(default)]
22 pub history: HistoryConfig,
23 #[serde(default)]
25 pub server: ServerConfig,
26 #[serde(default)]
28 pub logging: LoggingConfig,
29 #[serde(default)]
31 pub ui: ThemeConfig,
32 #[serde(default)]
34 pub tls: TlsConfig,
35}
36
37impl Config {
38 pub fn load(file: &str) -> Result<Self> {
44 let f = expand_file(file);
45 let data = read_to_string(&f).map_err(|err| Error::ReadConfigError {
46 filename: f,
47 source: err,
48 })?;
49 Self::deserialize_toml(&data)
50 }
51
52 pub fn read_to_string(file: &str) -> Result<String> {
58 let f = expand_file(file);
59 let data = read_to_string(&f).map_err(|err| Error::ReadConfigError {
60 filename: f,
61 source: err,
62 })?;
63 Ok(data)
64 }
65
66 fn deserialize_toml(data: &str) -> Result<Self> {
72 toml::from_str(data).map_err(Error::DeserializeConfigError)
73 }
74
75 pub fn serialize_toml(&self) -> Result<String> {
81 toml::to_string(self).map_err(Error::SerializeConfigError)
82 }
83
84 #[must_use]
87 pub fn includes(&self) -> Vec<String> {
88 self.includes.iter().map(|e| expand_path(e)).collect()
89 }
90
91 #[must_use]
94 pub fn files(&self) -> Vec<String> {
95 self.files.iter().map(|e| expand_file(e)).collect()
96 }
97}
98
99impl FromStr for Config {
100 type Err = Error;
101
102 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
108 Self::deserialize_toml(s)
109 }
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd)]
114pub struct ServerConfig {
115 pub default_address: Option<String>,
117 pub default_auth_header: Option<String>,
119}
120
121impl ServerConfig {
122 #[must_use]
123 pub fn new(default_address: &str, default_auth_header: &str) -> Self {
124 let default_address = if default_address.is_empty() {
125 None
126 } else {
127 Some(default_address.to_string())
128 };
129
130 let default_auth_header = if default_auth_header.is_empty() {
131 None
132 } else {
133 Some(default_auth_header.to_string())
134 };
135 Self {
136 default_address,
137 default_auth_header,
138 }
139 }
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd)]
144pub struct HistoryConfig {
145 #[serde(default)]
147 pub directory: String,
148 #[serde(default = "default_autosave")]
150 pub autosave: bool,
151 #[serde(default)]
153 pub disabled: bool,
154}
155
156impl Default for HistoryConfig {
157 fn default() -> Self {
158 Self {
159 directory: String::default(),
160 autosave: true,
161 disabled: false,
162 }
163 }
164}
165
166fn default_autosave() -> bool {
167 true
168}
169
170impl HistoryConfig {
171 #[must_use]
173 pub fn new(directory: &str, autosave: bool, disabled: bool) -> Self {
174 Self {
175 directory: directory.to_string(),
176 autosave,
177 disabled,
178 }
179 }
180
181 #[must_use]
184 pub fn directory_expanded(&self) -> String {
185 if self.directory.is_empty() {
186 return String::new();
187 }
188 expand_path(&self.directory)
189 }
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd)]
194pub struct LoggingConfig {
195 #[serde(default)]
197 pub level: LogLevel,
198 pub directory: String,
200}
201
202impl LoggingConfig {
203 #[must_use]
205 pub fn new(level: LogLevel, file_path: &str) -> Self {
206 Self {
207 level,
208 directory: file_path.to_string(),
209 }
210 }
211
212 #[must_use]
215 pub fn directory_expanded(&self) -> String {
216 if self.directory.is_empty() {
217 return String::new();
218 }
219 expand_path(&self.directory)
220 }
221
222 #[must_use]
225 pub fn file_path_expanded(&self) -> String {
226 let directory_expanded = self.directory_expanded();
227 let file_path = Path::new(&directory_expanded).join(Self::file_name());
228 file_path.to_string_lossy().to_string()
229 }
230
231 #[must_use]
232 pub(crate) fn file_name() -> String {
233 String::from("wireman.log")
234 }
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd)]
238pub struct TlsConfig {
239 pub use_native: Option<bool>,
240 pub custom_cert: Option<String>,
241}
242
243impl TlsConfig {
244 #[must_use]
245 pub fn new(use_native: bool) -> Self {
246 Self {
247 use_native: Some(use_native),
248 custom_cert: None,
249 }
250 }
251
252 #[must_use]
253 pub fn custom(custom: &str) -> Self {
254 Self {
255 use_native: None,
256 custom_cert: Some(custom.to_string()),
257 }
258 }
259}
260
261#[cfg(test)]
262mod test {
263 use super::*;
264
265 #[test]
266 fn test_deserialize_toml() {
267 let data = r#"
268 includes = [
269 "/Users/myworkspace"
270 ]
271 files = [
272 "api.proto",
273 "internal.proto"
274 ]
275 [server]
276 default_address = "http://localhost:50051"
277 [history]
278 directory = "/Users/test"
279 autosave = false
280 [logging]
281 directory = "/Users"
282 level = "Debug"
283 [tls]
284 custom_cert = "cert.pem"
285 [ui]
286 skin = "skin.toml"
287 "#;
288 let cfg = Config::deserialize_toml(&data).unwrap();
289 let expected = Config {
290 includes: vec!["/Users/myworkspace".to_string()],
291 files: vec!["api.proto".to_string(), "internal.proto".to_string()],
292 tls: TlsConfig::custom("cert.pem"),
293 server: ServerConfig::new("http://localhost:50051", ""),
294 logging: LoggingConfig::new(LogLevel::Debug, "/Users"),
295 history: HistoryConfig::new("/Users/test", false, false),
296 ui: theme::Config::new(Some(String::from("skin.toml"))),
297 };
298 assert_eq!(cfg, expected);
299 }
300
301 #[test]
302 fn test_serialize_toml() {
303 let cfg = Config {
304 includes: vec!["/Users/myworkspace".to_string()],
305 files: vec!["api.proto".to_string(), "internal.proto".to_string()],
306 tls: TlsConfig::default(),
307 server: ServerConfig::new("http://localhost:50051", ""),
308 logging: LoggingConfig::new(LogLevel::Debug, "/Users"),
309 history: HistoryConfig::new("/Users/test", false, false),
310 ui: theme::Config::new(Some(String::from("skin.toml"))),
311 };
312 let expected = r#"includes = ["/Users/myworkspace"]
313files = ["api.proto", "internal.proto"]
314
315[history]
316directory = "/Users/test"
317autosave = false
318disabled = false
319
320[server]
321default_address = "http://localhost:50051"
322
323[logging]
324level = "Debug"
325directory = "/Users"
326
327[ui]
328skin = "skin.toml"
329
330[tls]
331"#;
332 assert_eq!(cfg.serialize_toml().unwrap(), expected);
333 }
334
335 #[test]
336 fn test_shell_expand() {
337 let cfg = Config {
338 includes: vec!["$HOME/workspace".to_string()],
339 files: vec![],
340 tls: TlsConfig::default(),
341 server: ServerConfig::default(),
342 logging: LoggingConfig::default(),
343 history: HistoryConfig::default(),
344 ui: ThemeConfig::default(),
345 };
346 let got = cfg.includes();
347 let home = std::env::var("HOME").unwrap();
348 assert_eq!(got.first(), Some(&format!("{home}/workspace")));
349 }
350}