refactor: Update error messages w/ anyhow and thiserror (#216)

Refactoring and updating of error messages + tests to be more useful.
This commit is contained in:
Clement Tsang 2020-08-31 23:59:33 -04:00 committed by GitHub
parent 5ed573157c
commit a4ddd649e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 240 additions and 130 deletions

2
.cargo-husky/hooks/pre-push Executable file
View file

@ -0,0 +1,2 @@
echo "Running pre-push hook: cargo +nightly clippy -- -D clippy::all"
cargo +nightly clippy -- -D clippy::all

View file

@ -38,6 +38,7 @@
"curr",
"czvf",
"fpath",
"fract",
"gotop",
"gtop",
"haase",

28
Cargo.lock generated
View file

@ -27,6 +27,12 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "anyhow"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
[[package]]
name = "arc-swap"
version = "0.4.6"
@ -131,6 +137,7 @@ dependencies = [
name = "bottom"
version = "0.4.7"
dependencies = [
"anyhow",
"assert_cmd",
"backtrace",
"battery",
@ -151,6 +158,7 @@ dependencies = [
"regex",
"serde",
"sysinfo",
"thiserror",
"toml",
"tui",
"typed-builder",
@ -1273,6 +1281,26 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.0.1"

View file

@ -24,14 +24,17 @@ lto = "fat"
codegen-units = 1
[dependencies]
anyhow = "1.0.32"
battery = "0.7.6"
crossterm = "0.17"
chrono = "0.4.15"
crossterm = "0.17"
ctrlc = {version = "3.1", features = ["termination"]}
clap = "2.33"
dirs = "3.0.1"
futures = "0.3.5"
heim = "0.0.10"
itertools = "0.9.0"
libc = "0.2"
regex = "1.3"
sysinfo = "0.15.1"
toml = "0.5.6"
@ -41,8 +44,7 @@ backtrace = "0.3"
serde = {version = "1.0", features = ["derive"] }
unicode-segmentation = "1.6.0"
unicode-width = "0.1"
libc = "0.2"
ctrlc = {version = "3.1", features = ["termination"]}
thiserror = "1.0.20"
tui = {version = "0.9.5", features = ["crossterm"], default-features = false }
# For debugging only...
@ -82,5 +84,5 @@ output = "bottom_x86_64_installer.msi"
[dev-dependencies.cargo-husky]
version = "1"
default-features = false
features = ["prepush-hook", "run-cargo-clippy"]
default-features = false
features = ["user-hooks"]

View file

@ -509,7 +509,7 @@ Supported named colours are one of the following strings: `Reset, Black, Red, Gr
| Cursor colour | The cursor's colour | `cursor_color="#ffffff"` |
| Selected text colour | The colour of text that is selected | `scroll_entry_text_color="#ffffff"` |
| Selected text background colour | The background colour of text that is selected | `scroll_entry_bg_color="#ffffff"` |
| Battery bar colours | Colour used is based on percentage and no. of colours | `battery_colours=["green", "yellow", "red"]` |
| Battery bar colours | Colour used is based on percentage and no. of colours | `battery_colors=["green", "yellow", "red"]` |
#### Layout

View file

@ -253,11 +253,11 @@ fn read_proc<S: core::hash::BuildHasher>(
.splitn(2, '(')
.collect::<Vec<_>>()
.last()
.ok_or(BottomError::MinorError())?
.ok_or(BottomError::MinorError)?
.rsplitn(2, ')')
.collect::<Vec<_>>()
.last()
.ok_or(BottomError::MinorError())?
.ok_or(BottomError::MinorError)?
.to_string();
let command = {
let cmd = read_path_contents(&pid_stat.proc_cmdline_path)?;
@ -271,7 +271,7 @@ fn read_proc<S: core::hash::BuildHasher>(
.split(')')
.collect::<Vec<_>>()
.last()
.ok_or(BottomError::MinorError())?
.ok_or(BottomError::MinorError)?
.split_whitespace()
.collect::<Vec<&str>>();
let (process_state_char, process_state) = get_linux_process_state(&stat);

View file

@ -943,7 +943,25 @@ impl std::str::FromStr for BottomWidgetType {
"empty" => Ok(BottomWidgetType::Empty),
"battery" | "batt" => Ok(BottomWidgetType::Battery),
_ => Err(BottomError::ConfigError(format!(
"invalid widget type: {}", // FIXME: Make this more helpful, specify valid widget types (just go through the list)
"\"{}\" is an invalid widget name.
Supported widget names:
+--------------------------+
| cpu |
+--------------------------+
| mem, memory |
+--------------------------+
| net, network |
+--------------------------+
| proc, process, processes |
+--------------------------+
| temp, temperature |
+--------------------------+
| disk |
+--------------------------+
| batt, battery |
+--------------------------+
",
s
))),
}

View file

@ -3,7 +3,7 @@
#[macro_use]
extern crate log;
use bottom::{canvas, constants::*, data_conversion::*, options::*, utils::error, *};
use bottom::{canvas, constants::*, data_conversion::*, options::*, *};
use std::{
boxed::Box,
@ -17,6 +17,7 @@ use std::{
time::Duration,
};
use anyhow::{Context, Result};
use crossterm::{
event::EnableMouseCapture,
execute,
@ -24,18 +25,22 @@ use crossterm::{
};
use tui::{backend::CrosstermBackend, Terminal};
fn main() -> error::Result<()> {
fn main() -> Result<()> {
#[cfg(debug_assertions)]
{
utils::logging::init_logger()?;
}
let matches = clap::get_matches();
let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?;
let config_path = read_config(matches.value_of("CONFIG_LOCATION"))
.context("Unable to access the given config file location.")?;
let config: Config = create_or_get_config(&config_path)
.context("Unable to properly parse or create the config file.")?;
// Get widget layout separately
let (widget_layout, default_widget_id, default_widget_type_option) =
get_widget_layout(&matches, &config)?;
get_widget_layout(&matches, &config)
.context("Found an issue while trying to build the widget layout.")?;
// Create "app" struct, which will control most of the program and store settings/state
let mut app = build_app(

View file

@ -1,5 +1,4 @@
use tui::style::{Color, Style};
// use tui::style::Modifier;
use colour_utils::*;
@ -175,11 +174,10 @@ impl CanvasColours {
Ok(())
}
pub fn set_battery_colours(&mut self, colours: &[String]) -> error::Result<()> {
pub fn set_battery_colors(&mut self, colours: &[String]) -> error::Result<()> {
if colours.is_empty() {
Err(error::BottomError::ConfigError(
"invalid colour config: battery colour list must have at least one colour!"
.to_string(),
"battery colour list must have at least one colour.".to_string(),
))
} else {
let generated_colours: Result<Vec<_>, _> = colours

View file

@ -100,7 +100,7 @@ pub fn convert_hex_to_color(hex: &str) -> error::Result<Color> {
fn hex_err(hex: &str) -> error::Result<u8> {
Err(
error::BottomError::ConfigError(format!(
"invalid color hex: error when parsing hex value {}. It must be a valid 7 character hex string of the (ie: \"#112233\")."
"\"{}\" is an invalid hex colour. It must be a valid 7 character hex string of the (ie: \"#112233\")."
, hex))
)
}
@ -124,7 +124,7 @@ pub fn convert_hex_to_color(hex: &str) -> error::Result<Color> {
}
Err(error::BottomError::ConfigError(format!(
"invalid color hex: value {} is not of valid length. It must be a 7 character string of the form \"#112233\".",
"\"{}\" is an invalid hex colour. It must be a 7 character string of the form \"#112233\".",
hex
)))
}
@ -144,7 +144,7 @@ pub fn get_style_from_config(input_val: &str) -> error::Result<Style> {
}
} else {
Err(error::BottomError::ConfigError(format!(
"invalid color: value {} is not valid.",
"value \"{}\" is not valid.",
input_val
)))
}
@ -161,7 +161,7 @@ pub fn get_colour_from_config(input_val: &str) -> error::Result<Color> {
}
} else {
Err(error::BottomError::ConfigError(format!(
"invalid color: value {} is not valid.",
"value \"{}\" is not valid.",
input_val
)))
}
@ -175,7 +175,7 @@ fn convert_rgb_to_color(rgb_str: &str) -> error::Result<Color> {
let rgb_list = rgb_str.split(',').collect::<Vec<&str>>();
if rgb_list.len() != 3 {
return Err(error::BottomError::ConfigError(format!(
"invalid RGB color: value {} is not of valid length. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").",
"value \"{}\" is an invalid RGB colour. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").",
rgb_str
)));
}
@ -194,7 +194,7 @@ fn convert_rgb_to_color(rgb_str: &str) -> error::Result<Color> {
Ok(Color::Rgb(rgb[0], rgb[1], rgb[2]))
} else {
Err(error::BottomError::ConfigError(format!(
"invalid RGB color: value {} contained invalid RGB values. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").",
"value \"{}\" contained invalid RGB values. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").",
rgb_str
)))
}
@ -211,9 +211,23 @@ fn convert_name_to_color(color_name: &str) -> error::Result<Color> {
}
Err(error::BottomError::ConfigError(format!(
"invalid named color: value {} is not a supported named colour. The following are supported strings: \
Reset, Black, Red, Green, Yellow, Blue, Magenta, Cyan, Gray, DarkGray, LightRed, LightGreen, \
LightYellow, LightBlue, LightMagenta, LightCyan, White",
"\"{}\" is an invalid named colour.
The following are supported strings:
+--------+------------+--------------+
| Reset | Magenta | LightYellow |
+--------+------------+--------------+
| Black | Cyan | LightBlue |
+--------+------------+--------------+
| Red | Gray | LightMagenta |
+--------+------------+--------------+
| Green | DarkGray | LightCyan |
+--------+------------+--------------+
| Yellow | LightRed | White |
+--------+------------+--------------+
| Blue | LightGreen | |
+--------+------------+--------------+
",
color_name
)))
}

View file

@ -239,7 +239,7 @@ For example, suppose we have a layout that looks like:
Setting '--default_widget_type Temp' will make the Temperature
widget selected by default.
Supported widget types:
Supported widget names:
+--------------------------+
| cpu |
+--------------------------+

View file

@ -5,8 +5,10 @@ extern crate log;
use std::{
boxed::Box,
fs,
io::{stdout, Write},
panic::PanicInfo,
path::PathBuf,
thread,
time::{Duration, Instant},
};
@ -18,6 +20,8 @@ use crossterm::{
terminal::{disable_raw_mode, LeaveAlternateScreen},
};
use anyhow::Context;
use app::{
data_harvester::{self, processes::ProcessSorting},
layout_manager::{UsedWidgets, WidgetDirection},
@ -164,15 +168,14 @@ pub fn handle_key_event_or_break(
false
}
pub fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> {
use std::{ffi::OsString, fs};
let config_path = if let Some(conf_loc) = flag_config_location {
Some(OsString::from(conf_loc))
pub fn read_config(config_location: Option<&str>) -> error::Result<Option<PathBuf>> {
let config_path = if let Some(conf_loc) = config_location {
Some(PathBuf::from(conf_loc))
} else if cfg!(target_os = "windows") {
if let Some(home_path) = dirs::config_dir() {
let mut path = home_path;
path.push(DEFAULT_CONFIG_FILE_PATH);
Some(path.into_os_string())
Some(path)
} else {
None
}
@ -182,13 +185,13 @@ pub fn create_config(flag_config_location: Option<&str>) -> error::Result<Config
path.push(DEFAULT_CONFIG_FILE_PATH);
if path.exists() {
// If it already exists, use the old one.
Some(path.into_os_string())
Some(path)
} else {
// If it does not, use the new one!
if let Some(config_path) = dirs::config_dir() {
let mut path = config_path;
path.push(DEFAULT_CONFIG_FILE_PATH);
Some(path.into_os_string())
Some(path)
} else {
None
}
@ -197,9 +200,11 @@ pub fn create_config(flag_config_location: Option<&str>) -> error::Result<Config
None
};
if let Some(config_path) = config_path {
let path = std::path::Path::new(&config_path);
Ok(config_path)
}
pub fn create_or_get_config(config_path: &Option<PathBuf>) -> error::Result<Config> {
if let Some(path) = config_path {
if let Ok(config_string) = fs::read_to_string(path) {
Ok(toml::from_str(config_string.as_str())?)
} else {
@ -229,48 +234,76 @@ pub fn try_drawing(
pub fn generate_config_colours(
config: &Config, painter: &mut canvas::Painter,
) -> error::Result<()> {
) -> anyhow::Result<()> {
if let Some(colours) = &config.colors {
if let Some(border_color) = &colours.border_color {
painter.colours.set_border_colour(border_color)?;
painter
.colours
.set_border_colour(border_color)
.context("Update 'border_color' in your config file..")?;
}
if let Some(highlighted_border_color) = &colours.highlighted_border_color {
painter
.colours
.set_highlighted_border_colour(highlighted_border_color)?;
.set_highlighted_border_colour(highlighted_border_color)
.context("Update 'highlighted_border_color' in your config file..")?;
}
if let Some(text_color) = &colours.text_color {
painter.colours.set_text_colour(text_color)?;
painter
.colours
.set_text_colour(text_color)
.context("Update 'text_color' in your config file..")?;
}
if let Some(avg_cpu_color) = &colours.avg_cpu_color {
painter.colours.set_avg_cpu_colour(avg_cpu_color)?;
painter
.colours
.set_avg_cpu_colour(avg_cpu_color)
.context("Update 'avg_cpu_color' in your config file..")?;
}
if let Some(all_cpu_color) = &colours.all_cpu_color {
painter.colours.set_all_cpu_colour(all_cpu_color)?;
painter
.colours
.set_all_cpu_colour(all_cpu_color)
.context("Update 'all_cpu_color' in your config file..")?;
}
if let Some(cpu_core_colors) = &colours.cpu_core_colors {
painter.colours.set_cpu_colours(cpu_core_colors)?;
painter
.colours
.set_cpu_colours(cpu_core_colors)
.context("Update 'cpu_core_colors' in your config file..")?;
}
if let Some(ram_color) = &colours.ram_color {
painter.colours.set_ram_colour(ram_color)?;
painter
.colours
.set_ram_colour(ram_color)
.context("Update 'ram_color' in your config file..")?;
}
if let Some(swap_color) = &colours.swap_color {
painter.colours.set_swap_colour(swap_color)?;
painter
.colours
.set_swap_colour(swap_color)
.context("Update 'swap_color' in your config file..")?;
}
if let Some(rx_color) = &colours.rx_color {
painter.colours.set_rx_colour(rx_color)?;
painter
.colours
.set_rx_colour(rx_color)
.context("Update 'rx_color' in your config file..")?;
}
if let Some(tx_color) = &colours.tx_color {
painter.colours.set_tx_colour(tx_color)?;
painter
.colours
.set_tx_colour(tx_color)
.context("Update 'tx_color' in your config file..")?;
}
// if let Some(rx_total_color) = &colours.rx_total_color {
@ -284,33 +317,43 @@ pub fn generate_config_colours(
if let Some(table_header_color) = &colours.table_header_color {
painter
.colours
.set_table_header_colour(table_header_color)?;
.set_table_header_colour(table_header_color)
.context("Update 'table_header_color' in your config file..")?;
}
if let Some(scroll_entry_text_color) = &colours.selected_text_color {
painter
.colours
.set_scroll_entry_text_color(scroll_entry_text_color)?;
.set_scroll_entry_text_color(scroll_entry_text_color)
.context("Update 'selected_text_color' in your config file..")?;
}
if let Some(scroll_entry_bg_color) = &colours.selected_bg_color {
painter
.colours
.set_scroll_entry_bg_color(scroll_entry_bg_color)?;
.set_scroll_entry_bg_color(scroll_entry_bg_color)
.context("Update 'selected_bg_color' in your config file..")?;
}
if let Some(widget_title_color) = &colours.widget_title_color {
painter
.colours
.set_widget_title_colour(widget_title_color)?;
.set_widget_title_colour(widget_title_color)
.context("Update 'widget_title_color' in your config file..")?;
}
if let Some(graph_color) = &colours.graph_color {
painter.colours.set_graph_colour(graph_color)?;
painter
.colours
.set_graph_colour(graph_color)
.context("Update 'graph_color' in your config file..")?;
}
if let Some(battery_colors) = &colours.battery_colors {
painter.colours.set_battery_colours(battery_colors)?;
painter
.colours
.set_battery_colors(battery_colors)
.context("Update 'battery_colors' in your config file.")?;
}
}

View file

@ -12,6 +12,8 @@ use layout_options::*;
pub mod layout_options;
use anyhow::{Context, Result};
#[derive(Default, Deserialize)]
pub struct Config {
pub flags: Option<ConfigFlags>,
@ -70,10 +72,11 @@ pub struct ConfigColours {
pub fn build_app(
matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout,
default_widget_id: u64, default_widget_type_option: &Option<BottomWidgetType>,
) -> error::Result<App> {
) -> Result<App> {
use BottomWidgetType::*;
let autohide_time = get_autohide_time(&matches, &config);
let default_time_value = get_default_time_value(&matches, &config)?;
let default_time_value = get_default_time_value(&matches, &config)
.context("Update 'default_time_value' in your config file.")?;
let use_basic_mode = get_use_basic_mode(&matches, &config);
// For processes
@ -213,15 +216,18 @@ pub fn build_app(
};
let app_config_fields = AppConfigFields {
update_rate_in_milliseconds: get_update_rate_in_milliseconds(matches, config)?,
temperature_type: get_temperature(matches, config)?,
update_rate_in_milliseconds: get_update_rate_in_milliseconds(matches, config)
.context("Update 'rate' in your config file.")?,
temperature_type: get_temperature(matches, config)
.context("Update 'temperature_type' in your config file.")?,
show_average_cpu: get_show_average_cpu(matches, config),
use_dot: get_use_dot(matches, config),
left_legend: get_use_left_legend(matches, config),
use_current_cpu_total: get_use_current_cpu_total(matches, config),
use_basic_mode,
default_time_value,
time_interval: get_time_interval(matches, config)?,
time_interval: get_time_interval(matches, config)
.context("Update 'time_delta' in your config file.")?,
hide_time: get_hide_time(matches, config),
autohide_time,
use_old_network_legend: get_use_old_network_legend(matches, config),
@ -316,7 +322,7 @@ pub fn get_widget_layout(
ret_bottom_layout
} else {
return Err(error::BottomError::ConfigError(
"invalid layout config: please have at least one widget.".to_string(),
"please have at least one widget under the '[[row]]' section.".to_string(),
));
}
};
@ -340,12 +346,12 @@ fn get_update_rate_in_milliseconds(
};
if update_rate_in_milliseconds < 250 {
return Err(BottomError::InvalidArg(
"Please set your update rate to be at least 250 milliseconds.".to_string(),
return Err(BottomError::ConfigError(
"set your update rate to be at least 250 milliseconds.".to_string(),
));
} else if update_rate_in_milliseconds as u128 > std::u64::MAX as u128 {
return Err(BottomError::InvalidArg(
"Please set your update rate to be at most unsigned INT_MAX.".to_string(),
return Err(BottomError::ConfigError(
"set your update rate to be at most unsigned INT_MAX.".to_string(),
));
}
@ -368,11 +374,10 @@ fn get_temperature(
"fahrenheit" | "f" => Ok(data_harvester::temperature::TemperatureType::Fahrenheit),
"kelvin" | "k" => Ok(data_harvester::temperature::TemperatureType::Kelvin),
"celsius" | "c" => Ok(data_harvester::temperature::TemperatureType::Celsius),
_ => Err(BottomError::ConfigError(
"invalid temperature type: please have the value be of the form \
<kelvin|k|celsius|c|fahrenheit|f>"
.to_string(),
)),
_ => Err(BottomError::ConfigError(format!(
"\"{}\" is an invalid temperature type, use \"<kelvin|k|celsius|c|fahrenheit|f>\".",
temp_type
))),
};
}
}
@ -455,12 +460,12 @@ fn get_default_time_value(
};
if default_time < 30000 {
return Err(BottomError::InvalidArg(
"Please set your default value to be at least 30000 milliseconds.".to_string(),
return Err(BottomError::ConfigError(
"set your default value to be at least 30000 milliseconds.".to_string(),
));
} else if default_time as u128 > STALE_MAX_MILLISECONDS as u128 {
return Err(BottomError::InvalidArg(format!(
"Please set your default value to be at most {} milliseconds.",
return Err(BottomError::ConfigError(format!(
"set your default value to be at most {} milliseconds.",
STALE_MAX_MILLISECONDS
)));
}
@ -482,12 +487,12 @@ fn get_time_interval(matches: &clap::ArgMatches<'static>, config: &Config) -> er
};
if time_interval < 1000 {
return Err(BottomError::InvalidArg(
"Please set your time delta to be at least 1000 milliseconds.".to_string(),
return Err(BottomError::ConfigError(
"set your time delta to be at least 1000 milliseconds.".to_string(),
));
} else if time_interval > STALE_MAX_MILLISECONDS as u128 {
return Err(BottomError::InvalidArg(format!(
"Please set your time delta to be at most {} milliseconds.",
return Err(BottomError::ConfigError(format!(
"set your time delta to be at most {} milliseconds.",
STALE_MAX_MILLISECONDS
)));
}
@ -601,8 +606,8 @@ fn get_default_widget_and_count(
};
if widget_count > std::u64::MAX as u128 {
Err(BottomError::InvalidArg(
"Please set your widget count to be at most unsigned INT_MAX.".to_string(),
Err(BottomError::ConfigError(
"set your widget count to be at most unsigned INT_MAX.".to_string(),
))
} else {
Ok((widget_type, widget_count as u64))

View file

@ -1,60 +1,39 @@
use std::{borrow::Cow, result};
use thiserror::Error;
/// A type alias for handling errors related to Bottom.
pub type Result<T> = result::Result<T, BottomError>;
/// An error that can occur while Bottom runs.
#[derive(Debug)]
#[derive(Debug, Error)]
pub enum BottomError {
/// An error when there is an IO exception.
#[error("IO exception, {0}")]
InvalidIO(String),
/// An error when there is an invalid argument passed in.
InvalidArg(String),
/// An error when the heim library encounters a problem.
#[error("Error caused by Heim, {0}")]
InvalidHeim(String),
/// An error when the Crossterm library encounters a problem.
#[error("Error caused by Crossterm, {0}")]
CrosstermError(String),
/// An error to represent generic errors.
#[error("Generic error, {0}")]
GenericError(String),
/// An error to represent errors with fern.
#[error("Fern error, {0}")]
FernError(String),
/// An error to represent errors with the config.
#[error("Configuration file error, {0}")]
ConfigError(String),
/// An error to represent errors with converting between data types.
#[error("Conversion error, {0}")]
ConversionError(String),
/// An error to represent errors with querying.
#[error("Query error, {0}")]
QueryError(Cow<'static, str>),
/// An error that just signifies something minor went wrong; no message.
MinorError(),
}
impl std::fmt::Display for BottomError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
BottomError::InvalidIO(ref message) => {
write!(f, "encountered an IO exception: {}", message)
}
BottomError::InvalidArg(ref message) => write!(f, "Invalid argument: {}", message),
BottomError::InvalidHeim(ref message) => write!(
f,
"invalid error during data collection due to heim: {}",
message
),
BottomError::CrosstermError(ref message) => {
write!(f, "invalid error due to Crossterm: {}", message)
}
BottomError::GenericError(ref message) => write!(f, "{}", message),
BottomError::FernError(ref message) => write!(f, "Invalid fern error: {}", message),
BottomError::ConfigError(ref message) => {
write!(f, "invalid config file error: {}", message)
}
BottomError::ConversionError(ref message) => {
write!(f, "unable to convert: {}", message)
}
BottomError::QueryError(ref message) => write!(f, "{}", message),
BottomError::MinorError() => write!(f, "Minor error."),
}
}
#[error("Minor error.")]
MinorError,
}
impl From<std::io::Error> for BottomError {
@ -77,7 +56,7 @@ impl From<crossterm::ErrorKind> for BottomError {
impl From<std::num::ParseIntError> for BottomError {
fn from(err: std::num::ParseIntError) -> Self {
BottomError::InvalidArg(err.to_string())
BottomError::ConfigError(err.to_string())
}
}

View file

@ -18,7 +18,7 @@ fn test_small_rate() -> Result<(), Box<dyn std::error::Error>> {
.assert()
.failure()
.stderr(predicate::str::contains(
"Please set your update rate to be at least 250 milliseconds.",
"set your update rate to be at least 250 milliseconds.",
));
Ok(())
}
@ -31,7 +31,7 @@ fn test_large_default_time() -> Result<(), Box<dyn std::error::Error>> {
.assert()
.failure()
.stderr(predicate::str::contains(
"Please set your default value to be at most",
"set your default value to be at most",
));
Ok(())
}
@ -44,7 +44,7 @@ fn test_small_default_time() -> Result<(), Box<dyn std::error::Error>> {
.assert()
.failure()
.stderr(predicate::str::contains(
"Please set your default value to be at least",
"set your default value to be at least",
));
Ok(())
}
@ -57,7 +57,7 @@ fn test_large_delta_time() -> Result<(), Box<dyn std::error::Error>> {
.assert()
.failure()
.stderr(predicate::str::contains(
"Please set your time delta to be at most",
"set your time delta to be at most",
));
Ok(())
}
@ -70,7 +70,7 @@ fn test_small_delta_time() -> Result<(), Box<dyn std::error::Error>> {
.assert()
.failure()
.stderr(predicate::str::contains(
"Please set your time delta to be at least",
"set your time delta to be at least",
));
Ok(())
}
@ -83,7 +83,7 @@ fn test_large_rate() -> Result<(), Box<dyn std::error::Error>> {
.assert()
.failure()
.stderr(predicate::str::contains(
"Please set your update rate to be at most unsigned INT_MAX.",
"set your update rate to be at most unsigned INT_MAX.",
));
Ok(())
}
@ -136,7 +136,7 @@ fn test_invalid_default_widget_1() -> Result<(), Box<dyn std::error::Error>> {
.arg("fake_widget")
.assert()
.failure()
.stderr(predicate::str::contains("invalid widget type"));
.stderr(predicate::str::contains("invalid widget name"));
Ok(())
}
@ -151,7 +151,7 @@ fn test_invalid_default_widget_2() -> Result<(), Box<dyn std::error::Error>> {
.assert()
.failure()
.stderr(predicate::str::contains(
"Please set your widget count to be at most unsigned INT_MAX",
"set your widget count to be at most unsigned INT_MAX",
));
Ok(())

View file

@ -26,7 +26,7 @@ fn test_empty_layout() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/empty_layout.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid layout config"));
.stderr(predicate::str::contains("at least one widget"));
Ok(())
}
@ -37,7 +37,7 @@ fn test_invalid_layout_widget_type() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/invalid_layout_widget_type.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid widget type"));
.stderr(predicate::str::contains("invalid widget name"));
Ok(())
}
@ -62,7 +62,7 @@ fn test_invalid_colour_hex() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/invalid_colour_hex.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid color hex"));
.stderr(predicate::str::contains("invalid hex colour"));
Ok(())
}
@ -74,7 +74,7 @@ fn test_invalid_colour_hex_2() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/invalid_colour_hex_2.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid color hex"));
.stderr(predicate::str::contains("invalid hex colour"));
Ok(())
}
@ -87,7 +87,7 @@ fn test_invalid_colour_hex_3() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/invalid_colour_hex_3.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid color hex"));
.stderr(predicate::str::contains("invalid hex colour"));
Ok(())
}
@ -98,7 +98,7 @@ fn test_invalid_colour_name() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/invalid_colour_name.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid named color"));
.stderr(predicate::str::contains("invalid named colour"));
Ok(())
}
@ -109,7 +109,7 @@ fn test_invalid_colour_rgb() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/invalid_colour_rgb.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid RGB color"));
.stderr(predicate::str::contains("invalid RGB"));
Ok(())
}
@ -120,7 +120,7 @@ fn test_invalid_colour_rgb_2() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/invalid_colour_rgb_2.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid RGB color"));
.stderr(predicate::str::contains("invalid RGB"));
Ok(())
}
@ -131,6 +131,19 @@ fn test_invalid_colour_string() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/invalid_colour_string.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid named color"));
.stderr(predicate::str::contains("invalid named colour"));
Ok(())
}
#[test]
fn test_empty_battery() -> Result<(), Box<dyn std::error::Error>> {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/empty_battery.toml")
.assert()
.failure()
.stderr(predicate::str::contains(
"battery colour list must have at least one colour.",
));
Ok(())
}

View file

@ -0,0 +1,2 @@
[colors]
battery_colors=[]