ort_openrouter_cli/common/
error.rs

1//! ort: Open Router CLI
2//! https://github.com/grahamking/ort
3//!
4//! MIT License
5//! Copyright (c) 2025 Graham King
6
7use core::fmt;
8
9extern crate alloc;
10
11#[repr(u8)]
12#[derive(Debug, Clone, Copy)]
13pub enum ErrorKind {
14    // Configuration & arguments
15    //
16    MissingApiKey = 1,
17    // Argument parse error
18    InvalidArguments,
19    // Failed to parse config
20    ConfigParseFailed,
21    // Failed to read config file
22    ConfigReadFailed,
23    MissingHomeDir,
24
25    // Conversation/history
26    HistoryMissing,
27    HistoryParseFailed,
28    HistoryReadFailed,
29    HistoryLookupFailed,
30
31    // Input validation
32    InvalidMessageSchema,
33
34    // Output & streaming
35    //
36    StdoutWriteFailed,
37    QueueDesync,
38    // OpenRouter did not return usage stats
39    MissingUsageStats,
40    ResponseStreamError,
41    LastWriterError,
42
43    // Filesystem
44    FileCreateFailed,
45    FileReadFailed,
46    FileWriteFailed,
47    FileStatFailed,
48    DirOpenFailed,
49
50    // Threads
51    //
52    // Failed mmap allocating thread stack
53    ThreadStackAllocFailed,
54    // pthread_create failed
55    ThreadSpawnFailed,
56
57    // Networking
58    //
59    DnsResolveFailed,
60    // libc::socket failed
61    SocketCreateFailed,
62    // libc::connect failed
63    SocketConnectFailed,
64    SocketReadFailed,
65    SocketWriteFailed,
66
67    // Generic I/O
68    UnexpectedEof,
69
70    // HTTP chunked transfer decoding
71    //
72    // EOF while reading chunk size
73    ChunkedEofInSize,
74    // Error reading chunk size
75    ChunkedSizeReadError,
76    ChunkedInvalidSize,
77    // Error reading chunked data line
78    ChunkedDataReadError,
79
80    // HTTP / higher-level protocol
81    HttpStatusError,
82    HttpConnectError,
83
84    // TLS handshake / record processing
85    //
86    TlsExpectedHandshakeRecord,
87    TlsExpectedServerHello,
88    // Expected server to send dummy Change Cipher Spec
89    TlsExpectedChangeCipherSpec,
90    TlsExpectedEncryptedRecords,
91    TlsBadHandshakeFragment,
92    TlsFinishedVerifyFailed,
93    TlsUnsupportedCipher,
94    TlsAlertReceived,
95    TlsRecordTooShort,
96    TlsHandshakeHeaderTooShort,
97    TlsHandshakeBodyTooShort,
98    TlsServerHelloTooShort,
99    TlsServerHelloSessionIdInvalid,
100    TlsServerHelloExtTooShort,
101    TlsExtensionHeaderTooShort,
102    TlsExtensionLengthInvalid,
103    TlsKeyShareServerHelloInvalid,
104    TlsServerGroupUnsupported,
105    TlsKeyShareLengthInvalid,
106    TlsServerNotTls13,
107    TlsMissingServerKey,
108
109    // Misc
110    FormatError,
111    RateLimited,
112    Other,
113}
114
115pub type OrtResult<T> = Result<T, OrtError>;
116
117#[derive(Debug, Clone, Copy)]
118pub struct OrtError {
119    pub kind: ErrorKind,
120    pub context: &'static str,
121}
122
123pub fn ort_error(kind: ErrorKind, context: &'static str) -> OrtError {
124    OrtError { kind, context }
125}
126
127// In release mode we only have a more general error
128#[cfg(not(debug_assertions))]
129pub fn ort_from_err<E: core::fmt::Display>(
130    kind: ErrorKind,
131    context: &'static str,
132    _err: E,
133) -> OrtError {
134    ort_error(kind, context)
135}
136
137// In debug mode we print the error. All the generics makes for a larger binary.
138#[cfg(debug_assertions)]
139pub fn ort_from_err<E: core::fmt::Display>(
140    kind: ErrorKind,
141    context: &'static str,
142    err: E,
143) -> OrtError {
144    use crate::libc;
145    use alloc::ffi::CString;
146    use alloc::string::ToString;
147
148    let c_s = CString::new("\nERROR: ".to_string() + &err.to_string()).unwrap();
149    unsafe {
150        libc::write(2, c_s.as_ptr().cast(), c_s.count_bytes());
151    }
152
153    ort_error(kind, context)
154}
155
156impl OrtError {
157    #[cfg(debug_assertions)]
158    pub fn debug_print(&self) {
159        use crate::libc;
160        use alloc::ffi::CString;
161        use alloc::string::ToString;
162        let s = self.to_string();
163        let c_s = CString::new(s).unwrap();
164        unsafe {
165            libc::write(2, c_s.as_ptr().cast(), c_s.count_bytes());
166        }
167    }
168
169    #[cfg(not(debug_assertions))]
170    pub fn debug_print(&self) {}
171}
172
173impl core::error::Error for OrtError {}
174
175impl fmt::Display for OrtError {
176    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177        write!(f, "{:?}: {}", self.kind, self.context)
178    }
179}
180
181impl From<core::fmt::Error> for OrtError {
182    fn from(err: core::fmt::Error) -> OrtError {
183        // fmt::Error has no payload; treat as format error.
184        let _ = err;
185        ort_error(ErrorKind::FormatError, "")
186    }
187}
188
189pub trait Context<T, E> {
190    /// Wrap the error value with additional context.
191    fn context(self, context: &'static str) -> Result<T, OrtError>;
192}
193
194impl<T, E> Context<T, E> for Result<T, E>
195where
196    E: Into<OrtError>,
197{
198    /// Wrap the error value with additional context.
199    fn context(self, context: &'static str) -> OrtResult<T> {
200        match self {
201            Ok(ok) => Ok(ok),
202            Err(error) => {
203                let mut err: OrtError = error.into();
204                err.context = context;
205                Err(err)
206            }
207        }
208    }
209}