use std::fmt;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum ExitCategory {
Success,
Usage,
ConfigurationInvalid,
GuardRejected,
TargetNotFound,
OptimisticConflict,
OutcomeUnknown,
RepositoryUnavailable,
ConfirmationRequired,
DeadlineExceeded,
OutputFailure,
Internal,
}
impl ExitCategory {
#[must_use]
pub const fn code(self) -> u8 {
match self {
Self::Success => 0,
Self::Usage => 1,
Self::ConfigurationInvalid => 2,
Self::GuardRejected => 3,
Self::TargetNotFound => 4,
Self::OptimisticConflict => 5,
Self::OutcomeUnknown => 6,
Self::RepositoryUnavailable => 7,
Self::ConfirmationRequired => 8,
Self::DeadlineExceeded => 9,
Self::OutputFailure => 10,
Self::Internal => 70,
}
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Success => "SUCCESS",
Self::Usage => "USAGE",
Self::ConfigurationInvalid => "CONFIGURATION_INVALID",
Self::GuardRejected => "GUARD_REJECTED",
Self::TargetNotFound => "TARGET_NOT_FOUND",
Self::OptimisticConflict => "OPTIMISTIC_CONFLICT",
Self::OutcomeUnknown => "OUTCOME_UNKNOWN",
Self::RepositoryUnavailable => "REPOSITORY_UNAVAILABLE",
Self::ConfirmationRequired => "CONFIRMATION_REQUIRED",
Self::DeadlineExceeded => "DEADLINE_EXCEEDED",
Self::OutputFailure => "OUTPUT_FAILURE",
Self::Internal => "INTERNAL",
}
}
#[must_use]
pub const fn outcome(self) -> Outcome {
match self {
Self::Success => Outcome::Success,
Self::GuardRejected | Self::ConfirmationRequired => Outcome::Rejected,
Self::OptimisticConflict => Outcome::Conflict,
Self::OutcomeUnknown => Outcome::Unknown,
Self::Usage
| Self::ConfigurationInvalid
| Self::TargetNotFound
| Self::RepositoryUnavailable
| Self::DeadlineExceeded
| Self::OutputFailure
| Self::Internal => Outcome::Error,
}
}
#[must_use]
pub const fn all() -> &'static [Self] {
&[
Self::Success,
Self::Usage,
Self::ConfigurationInvalid,
Self::GuardRejected,
Self::TargetNotFound,
Self::OptimisticConflict,
Self::OutcomeUnknown,
Self::RepositoryUnavailable,
Self::ConfirmationRequired,
Self::DeadlineExceeded,
Self::OutputFailure,
Self::Internal,
]
}
}
impl fmt::Display for ExitCategory {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum Outcome {
Success,
Rejected,
Conflict,
Unknown,
Error,
}
impl Outcome {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Success => "success",
Self::Rejected => "rejected",
Self::Conflict => "conflict",
Self::Unknown => "unknown",
Self::Error => "error",
}
}
}
impl fmt::Display for Outcome {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic)]
use super::ExitCategory;
#[test]
fn codes_are_unique_and_ordered() {
let mut seen = Vec::new();
for category in ExitCategory::all() {
let code = category.code();
assert!(!seen.contains(&code), "exit code {code} is reused");
seen.push(code);
}
}
#[test]
fn published_codes_never_change() {
assert_eq!(ExitCategory::Success.code(), 0);
assert_eq!(ExitCategory::Usage.code(), 1);
assert_eq!(ExitCategory::ConfigurationInvalid.code(), 2);
assert_eq!(ExitCategory::GuardRejected.code(), 3);
assert_eq!(ExitCategory::TargetNotFound.code(), 4);
assert_eq!(ExitCategory::OptimisticConflict.code(), 5);
assert_eq!(ExitCategory::OutcomeUnknown.code(), 6);
assert_eq!(ExitCategory::RepositoryUnavailable.code(), 7);
assert_eq!(ExitCategory::ConfirmationRequired.code(), 8);
assert_eq!(ExitCategory::DeadlineExceeded.code(), 9);
assert_eq!(ExitCategory::OutputFailure.code(), 10);
assert_eq!(ExitCategory::Internal.code(), 70);
}
}