use super::ScoopError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorRenderPolicy {
Default,
Quiet,
}
impl ScoopError {
pub fn exit_code(&self) -> u8 {
match self {
Self::VerifyFailed { .. } | Self::DiffMismatch { .. } => 1,
Self::PyenvNotFound
| Self::PyenvEnvNotFound { .. }
| Self::VenvWrapperEnvNotFound { .. }
| Self::CondaEnvNotFound { .. }
| Self::CorruptedEnvironment { .. }
| Self::MigrationSourcesNotFound { .. } => 3,
Self::MigrationFailed { .. }
| Self::MigrationNameConflict { .. }
| Self::MigrationBatchFailed { .. } => 2,
_ => 1,
}
}
pub fn render_policy(&self) -> ErrorRenderPolicy {
match self {
Self::VerifyFailed { .. }
| Self::MigrationBatchFailed { .. }
| Self::DiffMismatch { .. } => ErrorRenderPolicy::Quiet,
_ => ErrorRenderPolicy::Default,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verify_failed_is_quiet_and_exits_one() {
let err = ScoopError::VerifyFailed { issues: 2 };
assert_eq!(err.exit_code(), 1);
assert_eq!(err.render_policy(), ErrorRenderPolicy::Quiet);
}
#[test]
fn source_discovery_variants_exit_three() {
let cases = [
ScoopError::PyenvNotFound,
ScoopError::PyenvEnvNotFound {
name: "x".to_string(),
},
ScoopError::VenvWrapperEnvNotFound {
name: "x".to_string(),
},
ScoopError::CondaEnvNotFound {
name: "x".to_string(),
},
ScoopError::CorruptedEnvironment {
name: "x".to_string(),
reason: "y".to_string(),
},
];
for err in cases {
assert_eq!(err.exit_code(), 3, "{err:?} should exit 3");
assert_eq!(err.render_policy(), ErrorRenderPolicy::Default);
}
}
#[test]
fn migration_internal_failures_exit_two() {
use std::path::PathBuf;
let cases = [
ScoopError::MigrationFailed {
reason: "boom".to_string(),
},
ScoopError::MigrationNameConflict {
name: "x".to_string(),
existing: PathBuf::from("/y"),
},
ScoopError::MigrationBatchFailed {
failed_count: 2,
conflict_count: 1,
},
];
for err in cases {
assert_eq!(err.exit_code(), 2, "{err:?} should exit 2");
}
}
#[test]
fn migration_batch_failed_renders_quiet() {
let err = ScoopError::MigrationBatchFailed {
failed_count: 1,
conflict_count: 0,
};
assert_eq!(err.render_policy(), ErrorRenderPolicy::Quiet);
}
#[test]
fn diff_mismatch_exits_one_and_renders_quiet() {
let err = ScoopError::DiffMismatch {
env_a: "a".to_string(),
env_b: "b".to_string(),
differences: 3,
};
assert_eq!(err.exit_code(), 1);
assert_eq!(err.render_policy(), ErrorRenderPolicy::Quiet);
}
#[test]
fn migration_sources_not_found_exits_three() {
let cases = [
ScoopError::MigrationSourcesNotFound { requested: None },
ScoopError::MigrationSourcesNotFound {
requested: Some("pyenv".to_string()),
},
];
for err in cases {
assert_eq!(err.exit_code(), 3, "{err:?} should exit 3");
assert_eq!(err.render_policy(), ErrorRenderPolicy::Default);
}
}
#[test]
fn generic_operational_variants_exit_one() {
let cases = [
ScoopError::UvNotFound,
ScoopError::UvCommandFailed {
command: "venv".to_string(),
message: "m".to_string(),
},
ScoopError::HomeNotFound,
ScoopError::VirtualenvNotFound {
name: "x".to_string(),
},
ScoopError::InvalidArgument {
message: "m".to_string(),
},
];
for err in cases {
assert_eq!(
err.exit_code(),
1,
"{err:?} should exit 1 under narrow policy"
);
assert_eq!(err.render_policy(), ErrorRenderPolicy::Default);
}
}
#[test]
fn render_policy_is_default_for_non_semantic() {
assert_eq!(
ScoopError::UvNotFound.render_policy(),
ErrorRenderPolicy::Default
);
assert_eq!(
ScoopError::PyenvNotFound.render_policy(),
ErrorRenderPolicy::Default
);
}
#[test]
fn render_policy_enum_is_value_type() {
let a = ErrorRenderPolicy::Quiet;
let b = ErrorRenderPolicy::Quiet;
assert_eq!(a, b);
let c: ErrorRenderPolicy = a;
assert_eq!(c, ErrorRenderPolicy::Quiet);
}
}