use camino::Utf8PathBuf;
use thiserror::Error;
use crate::domain::marker::MarkerError;
#[derive(Debug, Error)]
pub enum AppError {
#[error("usage: {0}")]
Usage(String),
#[error("{count} violation(s) found")]
Violations {
count: usize,
},
#[error("missing manifest: {0}")]
ManifestMissing(Utf8PathBuf),
#[error("invalid manifest: {0}")]
ManifestInvalid(String),
#[error(transparent)]
Marker(#[from] MarkerError),
#[error("{0}")]
Refused(String),
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
impl AppError {
#[must_use]
pub fn exit_code(&self) -> u8 {
match self {
Self::Violations { .. } => 1,
Self::Usage(_) => 64,
Self::ManifestInvalid(_) | Self::Marker(_) => 65,
Self::ManifestMissing(_) => 66,
Self::Refused(_) => 73,
Self::Io(e) if e.kind() == std::io::ErrorKind::NotFound => 66,
Self::Io(e) if e.kind() == std::io::ErrorKind::PermissionDenied => 77,
Self::Io(_) => 74,
Self::Other(_) => 70,
}
}
#[must_use]
pub const fn kind(&self) -> &'static str {
match self {
Self::Usage(_) => "Usage",
Self::Violations { .. } => "Violations",
Self::ManifestMissing(_) => "ManifestMissing",
Self::ManifestInvalid(_) => "ManifestInvalid",
Self::Marker(_) => "Marker",
Self::Refused(_) => "Refused",
Self::Io(_) => "Io",
Self::Other(_) => "Other",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn io(kind: std::io::ErrorKind) -> AppError {
AppError::Io(std::io::Error::from(kind))
}
#[test]
fn violations_are_one() {
assert_eq!(AppError::Violations { count: 3 }.exit_code(), 1);
}
#[test]
fn usage_is_sixty_four() {
assert_eq!(AppError::Usage("bad target".into()).exit_code(), 64);
}
#[test]
fn invalid_manifest_is_sixty_five() {
assert_eq!(
AppError::ManifestInvalid("schema_version 3".into()).exit_code(),
65
);
}
#[test]
fn malformed_marker_is_sixty_five() {
assert_eq!(AppError::Marker(MarkerError::Malformed).exit_code(), 65);
}
#[test]
fn missing_manifest_is_sixty_six() {
assert_eq!(
AppError::ManifestMissing("x/.spec-driven-docs/manifest.json".into()).exit_code(),
66
);
}
#[test]
fn refused_is_seventy_three() {
assert_eq!(
AppError::Refused("apply aborted; the target was restored".into()).exit_code(),
73
);
}
#[test]
fn not_found_io_is_sixty_six() {
assert_eq!(io(std::io::ErrorKind::NotFound).exit_code(), 66);
}
#[test]
fn permission_denied_io_is_seventy_seven() {
assert_eq!(io(std::io::ErrorKind::PermissionDenied).exit_code(), 77);
}
#[test]
fn other_io_is_seventy_four() {
assert_eq!(io(std::io::ErrorKind::BrokenPipe).exit_code(), 74);
}
#[test]
fn anyhow_is_seventy() {
assert_eq!(AppError::Other(anyhow::anyhow!("boom")).exit_code(), 70);
}
}