Skip to main content

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
7extern crate alloc;
8use alloc::string::String;
9
10#[repr(u8)]
11#[derive(Clone, Copy)]
12pub enum ErrorKind {
13    // Configuration & arguments
14    //
15    MissingApiKey = 1,
16    // Argument parse error
17    InvalidArguments,
18    // Failed to parse config
19    ConfigParseFailed,
20    // Failed to read config file
21    ConfigReadFailed,
22    MissingHomeDir,
23
24    // Conversation/history
25    HistoryMissing,
26    HistoryParseFailed,
27    HistoryReadFailed,
28    HistoryLookupFailed,
29
30    // Input validation
31    InvalidMessageSchema,
32
33    // Output & streaming
34    //
35    StdoutWriteFailed,
36    QueueDesync,
37    // OpenRouter did not return usage stats
38    MissingUsageStats,
39    ResponseStreamError,
40    LastWriterError,
41
42    // Filesystem
43    FileCreateFailed,
44    FileReadFailed,
45    FileWriteFailed,
46    FileStatFailed,
47    DirOpenFailed,
48
49    // Threads
50    //
51    // Failed mmap allocating thread stack
52    ThreadStackAllocFailed,
53    // pthread_create failed
54    ThreadSpawnFailed,
55
56    // Networking
57    //
58    DnsResolveFailed,
59    // libc::socket failed
60    SocketCreateFailed,
61    // libc::connect failed
62    SocketConnectFailed,
63    SocketReadFailed,
64    SocketWriteFailed,
65
66    // Generic I/O
67    UnexpectedEof,
68    // O_NONBLOCK socket has no data to read right now
69    WouldBlock,
70
71    // HTTP chunked transfer decoding
72    //
73    // EOF while reading chunk size
74    ChunkedEofInSize,
75    // Error reading chunk size
76    ChunkedSizeReadError,
77    ChunkedInvalidSize,
78    // Error reading chunked data line
79    ChunkedDataReadError,
80
81    // HTTP / higher-level protocol
82    HttpStatusError,
83    HttpConnectError,
84
85    // TLS handshake / record processing
86    //
87    TlsExpectedHandshakeRecord,
88    TlsExpectedServerHello,
89    // Expected server to send dummy Change Cipher Spec
90    TlsExpectedChangeCipherSpec,
91    TlsExpectedEncryptedRecords,
92    TlsBadHandshakeFragment,
93    TlsFinishedVerifyFailed,
94    TlsUnsupportedCipher,
95    TlsAlertReceived,
96    TlsRecordTooShort,
97    TlsHandshakeHeaderTooShort,
98    TlsHandshakeBodyTooShort,
99    TlsServerHelloTooShort,
100    TlsServerHelloSessionIdInvalid,
101    TlsServerHelloExtTooShort,
102    TlsExtensionHeaderTooShort,
103    TlsExtensionLengthInvalid,
104    TlsKeyShareServerHelloInvalid,
105    TlsServerGroupUnsupported,
106    TlsKeyShareLengthInvalid,
107    TlsServerNotTls13,
108    TlsMissingServerKey,
109    TlsAes128GcmDecryptFailed,
110
111    // Time
112    TscCpuidLeafUnavailable,
113    TscInvalidCalibration,
114    TscMissingCrystalClock,
115
116    // Misc
117    FormatError,
118    RateLimited,
119    Other,
120}
121
122impl ErrorKind {
123    pub fn as_string(&self) -> &'static str {
124        match self {
125            ErrorKind::MissingApiKey => "MissingApiKey",
126            ErrorKind::InvalidArguments => "InvalidArguments",
127            ErrorKind::ConfigParseFailed => "ConfigParseFailed",
128            ErrorKind::ConfigReadFailed => "ConfigReadFailed",
129            ErrorKind::MissingHomeDir => "MissingHomeDir",
130            ErrorKind::HistoryMissing => "HistoryMissing",
131            ErrorKind::HistoryParseFailed => "HistoryParseFailed",
132            ErrorKind::HistoryReadFailed => "HistoryReadFailed",
133            ErrorKind::HistoryLookupFailed => "HistoryLookupFailed",
134            ErrorKind::InvalidMessageSchema => "InvalidMessageSchema",
135            ErrorKind::StdoutWriteFailed => "StdoutWriteFailed",
136            ErrorKind::QueueDesync => "QueueDesync",
137            ErrorKind::MissingUsageStats => "MissingUsageStats",
138            ErrorKind::ResponseStreamError => "ResponseStreamError",
139            ErrorKind::LastWriterError => "LastWriterError",
140            ErrorKind::FileCreateFailed => "FileCreateFailed",
141            ErrorKind::FileReadFailed => "FileReadFailed",
142            ErrorKind::FileWriteFailed => "FileWriteFailed",
143            ErrorKind::FileStatFailed => "FileStatFailed",
144            ErrorKind::DirOpenFailed => "DirOpenFailed",
145            ErrorKind::ThreadStackAllocFailed => "ThreadStackAllocFailed",
146            ErrorKind::ThreadSpawnFailed => "ThreadSpawnFailed",
147            ErrorKind::DnsResolveFailed => "DnsResolveFailed",
148            ErrorKind::SocketCreateFailed => "SocketCreateFailed",
149            ErrorKind::SocketConnectFailed => "SocketConnectFailed",
150            ErrorKind::SocketReadFailed => "SocketReadFailed",
151            ErrorKind::SocketWriteFailed => "SocketWriteFailed",
152            ErrorKind::UnexpectedEof => "UnexpectedEof",
153            ErrorKind::WouldBlock => "WouldBlock",
154            ErrorKind::ChunkedEofInSize => "ChunkedEofInSize",
155            ErrorKind::ChunkedSizeReadError => "ChunkedSizeReadError",
156            ErrorKind::ChunkedInvalidSize => "ChunkedInvalidSize",
157            ErrorKind::ChunkedDataReadError => "ChunkedDataReadError",
158            ErrorKind::HttpStatusError => "HttpStatusError",
159            ErrorKind::HttpConnectError => "HttpConnectError",
160            ErrorKind::TlsExpectedHandshakeRecord => "TlsExpectedHandshakeRecord",
161            ErrorKind::TlsExpectedServerHello => "TlsExpectedServerHello",
162            ErrorKind::TlsExpectedChangeCipherSpec => "TlsExpectedChangeCipherSpec",
163            ErrorKind::TlsExpectedEncryptedRecords => "TlsExpectedEncryptedRecords",
164            ErrorKind::TlsBadHandshakeFragment => "TlsBadHandshakeFragment",
165            ErrorKind::TlsFinishedVerifyFailed => "TlsFinishedVerifyFailed",
166            ErrorKind::TlsUnsupportedCipher => "TlsUnsupportedCipher",
167            ErrorKind::TlsAlertReceived => "TlsAlertReceived",
168            ErrorKind::TlsRecordTooShort => "TlsRecordTooShort",
169            ErrorKind::TlsHandshakeHeaderTooShort => "TlsHandshakeHeaderTooShort",
170            ErrorKind::TlsHandshakeBodyTooShort => "TlsHandshakeBodyTooShort",
171            ErrorKind::TlsServerHelloTooShort => "TlsServerHelloTooShort",
172            ErrorKind::TlsServerHelloSessionIdInvalid => "TlsServerHelloSessionIdInvalid",
173            ErrorKind::TlsServerHelloExtTooShort => "TlsServerHelloExtTooShort",
174            ErrorKind::TlsExtensionHeaderTooShort => "TlsExtensionHeaderTooShort",
175            ErrorKind::TlsExtensionLengthInvalid => "TlsExtensionLengthInvalid",
176            ErrorKind::TlsKeyShareServerHelloInvalid => "TlsKeyShareServerHelloInvalid",
177            ErrorKind::TlsServerGroupUnsupported => "TlsServerGroupUnsupported",
178            ErrorKind::TlsKeyShareLengthInvalid => "TlsKeyShareLengthInvalid",
179            ErrorKind::TlsServerNotTls13 => "TlsServerNotTls13",
180            ErrorKind::TlsMissingServerKey => "TlsMissingServerKey",
181            ErrorKind::TlsAes128GcmDecryptFailed => "TlsAes128GcmDecryptFailed",
182
183            ErrorKind::TscCpuidLeafUnavailable => "TscCpuidLeafUnavailable",
184            ErrorKind::TscInvalidCalibration => "TscInvalidCalibration",
185            ErrorKind::TscMissingCrystalClock => "TscMissingCrystalClock",
186
187            ErrorKind::FormatError => "FormatError",
188            ErrorKind::RateLimited => "RateLimited",
189            ErrorKind::Other => "Other",
190        }
191    }
192}
193
194pub type OrtResult<T> = Result<T, OrtError>;
195
196#[derive(Clone, Copy)]
197pub struct OrtError {
198    pub kind: ErrorKind,
199    pub context: &'static str,
200}
201
202pub fn ort_error(kind: ErrorKind, context: &'static str) -> OrtError {
203    OrtError { kind, context }
204}
205
206impl OrtError {
207    pub fn as_string(&self) -> String {
208        let k = self.kind.as_string();
209        let mut out = String::with_capacity(k.len() + 2 + self.context.len());
210        out.push_str(k);
211        out.push_str(": ");
212        out.push_str(self.context);
213        out
214    }
215
216    #[cfg(debug_assertions)]
217    pub fn debug_print(&self) {
218        use crate::{syscall, utils::zclean};
219        use alloc::ffi::CString;
220        let mut s = self.as_string();
221        let c_s = CString::new(zclean(&mut s)).unwrap();
222        syscall::write(2, c_s.as_ptr().cast(), c_s.count_bytes());
223    }
224
225    #[cfg(not(debug_assertions))]
226    pub fn debug_print(&self) {}
227}
228
229pub trait Context<T, E> {
230    /// Wrap the error value with additional context.
231    fn context(self, context: &'static str) -> Result<T, OrtError>;
232}
233
234impl<T, E> Context<T, E> for Result<T, E>
235where
236    E: Into<OrtError>,
237{
238    /// Wrap the error value with additional context.
239    fn context(self, context: &'static str) -> OrtResult<T> {
240        match self {
241            Ok(ok) => Ok(ok),
242            Err(error) => {
243                let mut err: OrtError = error.into();
244                err.context = context;
245                Err(err)
246            }
247        }
248    }
249}