use std::process::Output;
use snafu::Snafu;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, Snafu, Clone)]
#[snafu(visibility(pub))]
pub enum Error {
#[snafu(display("jj command failed: {message}"))]
JjCommand {
message: String,
output: Option<Output>,
},
#[snafu(display("git command failed: {message}"))]
GitCommand {
message: String,
output: Option<Output>,
},
#[snafu(display("GitLab API error: {message}"))]
GitLabApi { message: String },
#[snafu(display("Configuration error: {message}"))]
Config { message: String },
#[snafu(display("Bookmark not found: {name}"))]
BookmarkNotFound { name: String },
#[snafu(display("Invalid bookmark graph: {message}"))]
InvalidGraph { message: String },
#[snafu(display("IO error: {message}"))]
Io { message: String },
#[snafu(display("HTTP request failed: {message}"))]
Http { message: String },
#[snafu(display("JSON error: {message}"))]
Json { message: String },
#[snafu(display("UTF-8 decoding error: {source}"))]
Utf8 { source: std::string::FromUtf8Error },
#[snafu(display("Parse error: {message}"))]
Parse { message: String },
#[snafu(display("{message}"))]
Other { message: String },
}
impl Error {
pub fn new(message: impl Into<String>) -> Self {
Error::Other {
message: message.into(),
}
}
}
impl From<std::io::Error> for Error {
fn from(source: std::io::Error) -> Self {
Error::Io {
message: source.to_string(),
}
}
}
impl From<reqwest::Error> for Error {
fn from(source: reqwest::Error) -> Self {
Error::Http {
message: source.to_string(),
}
}
}
impl From<serde_json::Error> for Error {
fn from(source: serde_json::Error) -> Self {
Error::Json {
message: source.to_string(),
}
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(source: std::string::FromUtf8Error) -> Self {
Error::Utf8 { source }
}
}
impl From<dialoguer::Error> for Error {
fn from(source: dialoguer::Error) -> Self {
Error::Io {
message: source.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_new() {
let err = Error::new("test error");
assert_eq!(err.to_string(), "test error");
}
#[test]
fn test_config_error() {
let err = Error::Config {
message: "missing config".to_string(),
};
assert_eq!(err.to_string(), "Configuration error: missing config");
}
#[test]
fn test_bookmark_not_found() {
let err = Error::BookmarkNotFound {
name: "feature".to_string(),
};
assert_eq!(err.to_string(), "Bookmark not found: feature");
}
#[test]
fn test_io_error_conversion() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err: Error = io_err.into();
assert!(err.to_string().contains("file not found"));
}
#[test]
fn test_utf8_error_conversion() {
let bytes = vec![0, 159, 146, 150]; let utf8_err = String::from_utf8(bytes).unwrap_err();
let err: Error = utf8_err.into();
assert!(err.to_string().contains("UTF-8 decoding error"));
}
#[test]
fn test_jj_command_error() {
let err = Error::JjCommand {
message: "command failed".to_string(),
output: None,
};
assert_eq!(err.to_string(), "jj command failed: command failed");
}
#[test]
fn test_gitlab_api_error() {
let err = Error::GitLabApi {
message: "API returned 404".to_string(),
};
assert_eq!(err.to_string(), "GitLab API error: API returned 404");
}
}