#![allow(clippy::module_name_repetitions)]
use crate::error::Error;
use crate::error::Result;
use crate::install::expand_file;
use crate::install::expand_path;
use logger::LogLevel;
use serde::{Deserialize, Serialize};
use std::fs::read_to_string;
use std::path::Path;
use theme::Config as ThemeConfig;
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct Config {
pub includes: Vec<String>,
pub files: Vec<String>,
#[serde(default)]
pub history: HistoryConfig,
#[serde(default)]
pub server: ServerConfig,
#[serde(default)]
pub logging: LoggingConfig,
#[serde(default)]
pub ui: ThemeConfig,
#[serde(default)]
pub tls: TlsConfig,
}
impl Config {
pub fn load(file: &str) -> Result<Self> {
let f = expand_file(file);
let data = read_to_string(&f).map_err(|err| Error::ReadConfigError {
filename: f,
source: err,
})?;
Self::deserialize_toml(&data)
}
fn deserialize_toml(data: &str) -> Result<Self> {
toml::from_str(data).map_err(Error::DeserializeConfigError)
}
pub fn serialize_toml(&self) -> Result<String> {
toml::to_string(self).map_err(Error::SerializeConfigError)
}
#[must_use]
pub fn includes(&self) -> Vec<String> {
self.includes.iter().map(|e| expand_path(e)).collect()
}
#[must_use]
pub fn files(&self) -> Vec<String> {
self.files.iter().map(|e| expand_file(e)).collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd)]
pub struct ServerConfig {
pub default_address: Option<String>,
pub default_auth_header: Option<String>,
}
impl ServerConfig {
#[must_use]
pub fn new(default_address: &str, default_auth_header: &str) -> Self {
let default_address = if default_address.is_empty() {
None
} else {
Some(default_address.to_string())
};
let default_auth_header = if default_auth_header.is_empty() {
None
} else {
Some(default_auth_header.to_string())
};
Self {
default_address,
default_auth_header,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd)]
pub struct HistoryConfig {
#[serde(default)]
pub directory: String,
#[serde(default = "default_autosave")]
pub autosave: bool,
#[serde(default)]
pub disabled: bool,
}
impl Default for HistoryConfig {
fn default() -> Self {
Self {
directory: String::default(),
autosave: true,
disabled: false,
}
}
}
fn default_autosave() -> bool {
true
}
impl HistoryConfig {
#[must_use]
pub fn new(directory: &str, autosave: bool, disabled: bool) -> Self {
Self {
directory: directory.to_string(),
autosave,
disabled,
}
}
#[must_use]
pub fn directory_expanded(&self) -> String {
if self.directory.is_empty() {
return String::new();
}
expand_path(&self.directory)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd)]
pub struct LoggingConfig {
#[serde(default)]
pub level: LogLevel,
pub directory: String,
}
impl LoggingConfig {
#[must_use]
pub fn new(level: LogLevel, file_path: &str) -> Self {
Self {
level,
directory: file_path.to_string(),
}
}
#[must_use]
pub fn directory_expanded(&self) -> String {
if self.directory.is_empty() {
return String::new();
}
expand_path(&self.directory)
}
#[must_use]
pub fn file_path_expanded(&self) -> String {
let directory_expanded = self.directory_expanded();
let file_path = Path::new(&directory_expanded).join(Self::file_name());
file_path.to_string_lossy().to_string()
}
#[must_use]
pub(crate) fn file_name() -> String {
String::from("wireman.log")
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd)]
pub struct TlsConfig {
pub use_native: Option<bool>,
pub custom_cert: Option<String>,
}
impl TlsConfig {
#[must_use]
pub fn new(use_native: bool) -> Self {
Self {
use_native: Some(use_native),
custom_cert: None,
}
}
#[must_use]
pub fn custom(custom: &str) -> Self {
Self {
use_native: None,
custom_cert: Some(custom.to_string()),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_deserialize_toml() {
let data = r#"
includes = [
"/Users/myworkspace"
]
files = [
"api.proto",
"internal.proto"
]
[server]
default_address = "http://localhost:50051"
[history]
directory = "/Users/test"
autosave = false
[logging]
directory = "/Users"
level = "Debug"
[tls]
custom_cert = "cert.pem"
[ui]
skin = "skin.toml"
"#;
let cfg = Config::deserialize_toml(&data).unwrap();
let expected = Config {
includes: vec!["/Users/myworkspace".to_string()],
files: vec!["api.proto".to_string(), "internal.proto".to_string()],
tls: TlsConfig::custom("cert.pem"),
server: ServerConfig::new("http://localhost:50051", ""),
logging: LoggingConfig::new(LogLevel::Debug, "/Users"),
history: HistoryConfig::new("/Users/test", false, false),
ui: theme::Config::new(Some(String::from("skin.toml"))),
};
assert_eq!(cfg, expected);
}
#[test]
fn test_serialize_toml() {
let cfg = Config {
includes: vec!["/Users/myworkspace".to_string()],
files: vec!["api.proto".to_string(), "internal.proto".to_string()],
tls: TlsConfig::default(),
server: ServerConfig::new("http://localhost:50051", ""),
logging: LoggingConfig::new(LogLevel::Debug, "/Users"),
history: HistoryConfig::new("/Users/test", false, false),
ui: theme::Config::new(Some(String::from("skin.toml"))),
};
let expected = r#"includes = ["/Users/myworkspace"]
files = ["api.proto", "internal.proto"]
[history]
directory = "/Users/test"
autosave = false
disabled = false
[server]
default_address = "http://localhost:50051"
[logging]
level = "Debug"
directory = "/Users"
[ui]
skin = "skin.toml"
[tls]
"#;
assert_eq!(cfg.serialize_toml().unwrap(), expected);
}
#[test]
fn test_shell_expand() {
let cfg = Config {
includes: vec!["$HOME/workspace".to_string()],
files: vec![],
tls: TlsConfig::default(),
server: ServerConfig::default(),
logging: LoggingConfig::default(),
history: HistoryConfig::default(),
ui: ThemeConfig::default(),
};
let got = cfg.includes();
let home = std::env::var("HOME").unwrap();
assert_eq!(got.first(), Some(&format!("{home}/workspace")));
}
}