worm_hole 3.0.2

CLI tool to easily jump between directories
// Copyright (C) 2025 Rignchen
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use std::{env::var as env, fmt::Display, process::ExitCode};

/// List of errors that the program can return.
#[derive(Debug)]
pub enum WHError {
	DatabaseConnectionError(String),
	AliasNotFound(String),
	AliasAlreadyExists(String),
	PathOfAliasNotExist(String, String),
	PatternNotMatch(String),
	Err(String, i32),
}

impl Display for WHError {
	#[rustfmt::skip]
	/// Display the error message.
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		let wh_alias: String = env("WH_ALIAS").unwrap_or_else(|_| "wormhole".to_string());

		match self {
			WHError::DatabaseConnectionError(path) => write!(f, "Error connecting to database at {}", path),
			WHError::AliasNotFound(alias) => write!(f, "Alias {} does not exist", alias),
			WHError::AliasAlreadyExists(alias) => write!(f, "Cannot create alias {} because it already exists", alias),
			WHError::PathOfAliasNotExist(alias, path) => write!(f, "The path {} does no longer exist\nRun `{wh_alias} rm {}` to remove the alias or `{wh_alias} edit {} <new_path>` to update the path", path, alias, alias),
			WHError::PatternNotMatch(pattern) => write!(f, "The pattern {} does not match anything", pattern),
			WHError::Err(msg, _) => write!(f, "{}", msg),
		}
	}
}

/// Result type which wither take a type T or a WHError.
pub type WHResult<T> = std::result::Result<T, WHError>;

/// Unwrap the WHResult and print the error message if it is an error.
pub fn get_exit_code(result: WHResult<()>) -> ExitCode {
	match result {
		Ok(_) => ExitCode::SUCCESS,
		Err(error) => {
			eprintln!("{}", error);
			ExitCode::from(match error {
				WHError::Err(_, code) => code as u8,
				WHError::DatabaseConnectionError(_) => 2,
				_ => 1,
			})
		}
	}
}

#[cfg(test)]
mod test {
	use super::*;
	use std::env::set_var;

	#[test]
	fn format_error() {
		set_var("WH_ALIAS", "wh");
		let error = WHError::DatabaseConnectionError("`arg`".to_string());
		assert_eq!(format!("{}", error), "Error connecting to database at `arg`");
		let error = WHError::AliasNotFound("`arg`".to_string());
		assert_eq!(format!("{}", error), "Alias `arg` does not exist");
		let error = WHError::AliasAlreadyExists("`arg`".to_string());
		assert_eq!(format!("{}", error), "Cannot create alias `arg` because it already exists");
		let error = WHError::PathOfAliasNotExist("`arg1`".to_string(), "`arg2`".to_string());
		assert_eq!(format!("{}", error), "The path `arg2` does no longer exist\nRun `wh rm `arg1`` to remove the alias or `wh edit `arg1` <new_path>` to update the path");
		let error = WHError::PatternNotMatch("`arg`".to_string());
		assert_eq!(format!("{}", error), "The pattern `arg` does not match anything");
	}

	mod path_of_alias_not_exist_uses_env {
		use super::*;
		use std::env::remove_var;

		#[test]
		fn with_default_env() {
			set_var("WH_ALIAS", "wh");
			let error = WHError::PathOfAliasNotExist("alias".to_string(), "path".to_string());
			assert_eq!(format!("{}", error), "The path path does no longer exist\nRun `wh rm alias` to remove the alias or `wh edit alias <new_path>` to update the path");
		}

		#[test]
		fn with_custom_env() {
			set_var("WH_ALIAS", "custom");
			let error = WHError::PathOfAliasNotExist("alias".to_string(), "path".to_string());
			assert_eq!(format!("{}", error), "The path path does no longer exist\nRun `custom rm alias` to remove the alias or `custom edit alias <new_path>` to update the path");
		}

		#[test]
		fn with_no_env() {
			remove_var("WH_ALIAS");
			let error = WHError::PathOfAliasNotExist("alias".to_string(), "path".to_string());
			assert_eq!(format!("{}", error), "The path path does no longer exist\nRun `wormhole rm alias` to remove the alias or `wormhole edit alias <new_path>` to update the path");
		}
	}

	#[test]
	fn get_exit_code_success() {
		let result: WHResult<()> = Ok(());
		assert_eq!(get_exit_code(result), ExitCode::SUCCESS);
	}

	#[test]
	fn get_exit_code_error() {
		let result: WHResult<()> = Err(WHError::Err("test error".to_string(), 42));
		assert_eq!(get_exit_code(result), ExitCode::from(42));
	}
}