aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-core/src/exit_code.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/mozart-core/src/exit_code.rs')
-rw-r--r--crates/mozart-core/src/exit_code.rs114
1 files changed, 114 insertions, 0 deletions
diff --git a/crates/mozart-core/src/exit_code.rs b/crates/mozart-core/src/exit_code.rs
new file mode 100644
index 0000000..bc01cfa
--- /dev/null
+++ b/crates/mozart-core/src/exit_code.rs
@@ -0,0 +1,114 @@
+/// Exit code: success.
+pub const OK: i32 = 0;
+
+/// Exit code: general / unclassified error.
+pub const GENERAL_ERROR: i32 = 1;
+
+/// Exit code: dependency resolution failed.
+pub const DEPENDENCY_RESOLUTION_FAILED: i32 = 2;
+
+/// Exit code: partial update requested but no lock file exists.
+pub const NO_LOCK_FILE_FOR_PARTIAL_UPDATE: i32 = 3;
+
+/// Exit code: lock file is invalid or corrupt.
+pub const LOCK_FILE_INVALID: i32 = 4;
+
+/// Exit code: audit found a security advisory.
+pub const AUDIT_FAILED: i32 = 5;
+
+/// Exit code: HTTP / network transport error.
+pub const TRANSPORT_ERROR: i32 = 100;
+
+// ---------------------------------------------------------------------------
+// MozartError — carries a specific exit code through anyhow's error chain
+// ---------------------------------------------------------------------------
+
+/// An error type that carries a specific exit code for Mozart to use on exit.
+///
+/// Use [`bail`] or [`bail_silent`] to construct one wrapped in `anyhow::Error`.
+#[derive(Debug)]
+pub struct MozartError {
+ pub message: String,
+ pub exit_code: i32,
+}
+
+impl std::fmt::Display for MozartError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.message)
+ }
+}
+
+impl std::error::Error for MozartError {}
+
+/// Return an `anyhow::Error` that carries `exit_code` and prints `message`.
+pub fn bail(exit_code: i32, message: impl Into<String>) -> anyhow::Error {
+ MozartError {
+ message: message.into(),
+ exit_code,
+ }
+ .into()
+}
+
+/// Return an `anyhow::Error` that carries `exit_code` but suppresses the
+/// message (caller has already printed it).
+pub fn bail_silent(exit_code: i32) -> anyhow::Error {
+ MozartError {
+ message: String::new(),
+ exit_code,
+ }
+ .into()
+}
+
+// ---------------------------------------------------------------------------
+// Tests
+// ---------------------------------------------------------------------------
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_constants_have_expected_values() {
+ assert_eq!(OK, 0);
+ assert_eq!(GENERAL_ERROR, 1);
+ assert_eq!(DEPENDENCY_RESOLUTION_FAILED, 2);
+ assert_eq!(NO_LOCK_FILE_FOR_PARTIAL_UPDATE, 3);
+ assert_eq!(LOCK_FILE_INVALID, 4);
+ assert_eq!(AUDIT_FAILED, 5);
+ assert_eq!(TRANSPORT_ERROR, 100);
+ }
+
+ #[test]
+ fn test_mozart_error_display() {
+ let err = MozartError {
+ message: "something went wrong".to_string(),
+ exit_code: GENERAL_ERROR,
+ };
+ assert_eq!(format!("{err}"), "something went wrong");
+ }
+
+ #[test]
+ fn test_bail_can_be_downcast() {
+ let err = bail(DEPENDENCY_RESOLUTION_FAILED, "cannot resolve");
+ let me = err.downcast_ref::<MozartError>().expect("should downcast");
+ assert_eq!(me.exit_code, DEPENDENCY_RESOLUTION_FAILED);
+ assert_eq!(me.message, "cannot resolve");
+ }
+
+ #[test]
+ fn test_bail_silent_has_empty_message() {
+ let err = bail_silent(GENERAL_ERROR);
+ let me = err.downcast_ref::<MozartError>().expect("should downcast");
+ assert_eq!(me.exit_code, GENERAL_ERROR);
+ assert!(me.message.is_empty());
+ }
+
+ #[test]
+ fn test_mozart_error_is_std_error() {
+ let err: Box<dyn std::error::Error> = Box::new(MozartError {
+ message: "test".to_string(),
+ exit_code: 1,
+ });
+ assert_eq!(err.to_string(), "test");
+ }
+}