use std::path::PathBuf;
#[derive(Debug, thiserror::Error)]
pub enum RepographError {
#[error("i/o error: {0}")]
Io(#[from] std::io::Error),
#[error("invalid TOML in config file: {0}")]
ConfigParse(#[from] toml::de::Error),
#[error("failed to serialize config: {0}")]
ConfigWrite(#[from] toml::ser::Error),
#[error("not a git repository: {path}: {source}")]
GitOpen {
path: PathBuf,
#[source]
source: git2::Error,
},
#[error("{kind} '{name}' not found")]
NotFound { kind: &'static str, name: String },
#[error("{kind} '{name}' already registered")]
Conflict { kind: &'static str, name: String },
#[error("permission denied: {path}")]
PermissionDenied { path: PathBuf },
#[error("{0}")]
UsageError(String),
#[error("invalid {kind} name '{name}': {reason}")]
InvalidName {
kind: &'static str,
name: String,
reason: &'static str,
},
#[error("{0}")]
NeedsInit(String),
#[error("doctor found {count} error finding(s) — see report above")]
DoctorErrorsFound { count: u32 },
}
impl RepographError {
#[must_use]
pub fn exit_code(&self) -> u8 {
match self {
Self::Io(e) if e.kind() == std::io::ErrorKind::PermissionDenied => 4,
Self::PermissionDenied { .. } => 4,
Self::GitOpen { .. } | Self::NotFound { .. } => 3,
Self::Conflict { .. } => 5,
Self::InvalidName { .. } | Self::NeedsInit { .. } | Self::UsageError(_) => 2,
Self::Io(_)
| Self::ConfigParse(_)
| Self::ConfigWrite(_)
| Self::DoctorErrorsFound { .. } => 1,
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn io_permission_denied_maps_to_4() {
let err = RepographError::Io(std::io::Error::from(std::io::ErrorKind::PermissionDenied));
assert_eq!(err.exit_code(), 4);
}
#[test]
fn other_io_maps_to_1() {
let err = RepographError::Io(std::io::Error::from(std::io::ErrorKind::NotFound));
assert_eq!(err.exit_code(), 1);
}
#[test]
fn explicit_permission_denied_maps_to_4() {
let err = RepographError::PermissionDenied {
path: PathBuf::from("/nope"),
};
assert_eq!(err.exit_code(), 4);
}
#[test]
fn not_found_maps_to_3() {
let err = RepographError::NotFound {
kind: "repo",
name: "foo".into(),
};
assert_eq!(err.exit_code(), 3);
}
#[test]
fn git_open_maps_to_3() {
let err = RepographError::GitOpen {
path: PathBuf::from("/tmp/x"),
source: git2::Error::from_str("synthetic"),
};
assert_eq!(err.exit_code(), 3);
}
#[test]
fn conflict_maps_to_5() {
let err = RepographError::Conflict {
kind: "name",
name: "foo".into(),
};
assert_eq!(err.exit_code(), 5);
}
#[test]
fn usage_error_maps_to_2() {
let err = RepographError::UsageError("nope".into());
assert_eq!(err.exit_code(), 2);
}
#[test]
fn invalid_name_maps_to_2() {
let err = RepographError::InvalidName {
kind: "workspace",
name: "Bad Name".into(),
reason: "must be lowercase",
};
assert_eq!(err.exit_code(), 2);
}
#[test]
fn needs_init_maps_to_2() {
let err = RepographError::NeedsInit("agents not configured; run `repograph init`".into());
assert_eq!(err.exit_code(), 2);
assert!(err.to_string().contains("repograph init"));
}
#[test]
fn config_parse_maps_to_1() {
let err: RepographError = toml::from_str::<toml::Value>("[unterminated")
.unwrap_err()
.into();
assert_eq!(err.exit_code(), 1);
}
}