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}