Skip to main content

sqry_cli/
error.rs

1//! CLI error types with custom exit codes
2//!
3//! This module defines error types that map to specific exit codes:
4//! - 0: Success
5//! - 1: Runtime error (default)
6//! - 2: Validation error (index corruption)
7//! - 65: ONNX Runtime missing (`EX_DATAERR` per BSD `sysexits.h`)
8//! - N: Pager exit code (when pager exits with non-zero)
9
10use std::fmt;
11
12/// CLI-specific error type with custom exit codes
13#[derive(Debug)]
14pub enum CliError {
15    /// Runtime error (exit code 1)
16    RuntimeError(anyhow::Error),
17
18    /// Pager exited with non-zero status (exit code from pager)
19    ///
20    /// This is used to propagate pager exit codes to the CLI exit code.
21    /// For example, if the user kills the pager with Ctrl-C (SIGINT),
22    /// this would propagate exit code 130 (128 + 2).
23    PagerExit(i32),
24
25    /// ONNX Runtime shared library not available (exit code 65,
26    /// `EX_DATAERR` per BSD `sysexits.h`).
27    ///
28    /// Surfaced by `sqry ask` when the NL classifier's model load fails
29    /// because `libonnxruntime` cannot be located on the host. The
30    /// `hint` payload is platform-specific install guidance produced by
31    /// [`sqry_nl::onnx_runtime_install_hint`] and rendered as a
32    /// two-line stderr message:
33    ///
34    /// ```text
35    /// error: ONNX Runtime not found
36    /// hint: <platform hint>
37    /// ```
38    OnnxRuntimeMissing { hint: String },
39}
40
41impl CliError {
42    /// Returns the appropriate exit code for this error
43    #[must_use]
44    pub fn exit_code(&self) -> i32 {
45        match self {
46            CliError::RuntimeError(_) => 1,
47            CliError::PagerExit(code) => *code,
48            // `EX_DATAERR` per BSD `sysexits.h`: the user-supplied
49            // execution environment is wrong (missing dylib).
50            CliError::OnnxRuntimeMissing { .. } => 65,
51        }
52    }
53
54    /// Create a runtime error
55    #[allow(dead_code)]
56    #[must_use]
57    pub fn runtime(err: impl Into<anyhow::Error>) -> Self {
58        CliError::RuntimeError(err.into())
59    }
60
61    /// Create a pager exit error
62    ///
63    /// Use this when the pager exits with a non-zero status and you want
64    /// to propagate that exit code to the CLI.
65    #[must_use]
66    pub fn pager_exit(code: i32) -> Self {
67        CliError::PagerExit(code)
68    }
69}
70
71impl fmt::Display for CliError {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            CliError::RuntimeError(err) => write!(f, "{err}"),
75            CliError::PagerExit(code) => write!(f, "pager exited with code {code}"),
76            CliError::OnnxRuntimeMissing { hint } => {
77                // Multi-line stderr surface — see `handle_cli_error` in
78                // `main.rs` for the full rendering. Display impl keeps
79                // the single-line "Error: <…>" path useful for logging
80                // / `anyhow` chains.
81                write!(f, "ONNX Runtime not found: {hint}")
82            }
83        }
84    }
85}
86
87impl std::error::Error for CliError {
88    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
89        match self {
90            CliError::RuntimeError(err) => err.source(),
91            CliError::PagerExit(_) | CliError::OnnxRuntimeMissing { .. } => None,
92        }
93    }
94}
95
96// Allow converting anyhow::Error to CliError (defaults to RuntimeError)
97impl From<anyhow::Error> for CliError {
98    fn from(err: anyhow::Error) -> Self {
99        CliError::RuntimeError(err)
100    }
101}