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
69    // HTTP chunked transfer decoding
70    //
71    // EOF while reading chunk size
72    ChunkedEofInSize,
73    // Error reading chunk size
74    ChunkedSizeReadError,
75    ChunkedInvalidSize,
76    // Error reading chunked data line
77    ChunkedDataReadError,
78
79    // HTTP / higher-level protocol
80    HttpStatusError,
81    HttpConnectError,
82
83    // TLS handshake / record processing
84    //
85    TlsExpectedHandshakeRecord,
86    TlsExpectedServerHello,
87    // Expected server to send dummy Change Cipher Spec
88    TlsExpectedChangeCipherSpec,
89    TlsExpectedEncryptedRecords,
90    TlsBadHandshakeFragment,
91    TlsFinishedVerifyFailed,
92    TlsUnsupportedCipher,
93    TlsAlertReceived,
94    TlsRecordTooShort,
95    TlsHandshakeHeaderTooShort,
96    TlsHandshakeBodyTooShort,
97    TlsServerHelloTooShort,
98    TlsServerHelloSessionIdInvalid,
99    TlsServerHelloExtTooShort,
100    TlsExtensionHeaderTooShort,
101    TlsExtensionLengthInvalid,
102    TlsKeyShareServerHelloInvalid,
103    TlsServerGroupUnsupported,
104    TlsKeyShareLengthInvalid,
105    TlsServerNotTls13,
106    TlsMissingServerKey,
107    TlsAes128GcmDecryptFailed,
108
109    // Misc
110    FormatError,
111    RateLimited,
112    Other,
113}
114
115impl ErrorKind {
116    pub fn as_string(&self) -> &'static str {
117        match self {
118            ErrorKind::MissingApiKey => "MissingApiKey",
119            ErrorKind::InvalidArguments => "InvalidArguments",
120            ErrorKind::ConfigParseFailed => "ConfigParseFailed",
121            ErrorKind::ConfigReadFailed => "ConfigReadFailed",
122            ErrorKind::MissingHomeDir => "MissingHomeDir",
123            ErrorKind::HistoryMissing => "HistoryMissing",
124            ErrorKind::HistoryParseFailed => "HistoryParseFailed",
125            ErrorKind::HistoryReadFailed => "HistoryReadFailed",
126            ErrorKind::HistoryLookupFailed => "HistoryLookupFailed",
127            ErrorKind::InvalidMessageSchema => "InvalidMessageSchema",
128            ErrorKind::StdoutWriteFailed => "StdoutWriteFailed",
129            ErrorKind::QueueDesync => "QueueDesync",
130            ErrorKind::MissingUsageStats => "MissingUsageStats",
131            ErrorKind::ResponseStreamError => "ResponseStreamError",
132            ErrorKind::LastWriterError => "LastWriterError",
133            ErrorKind::FileCreateFailed => "FileCreateFailed",
134            ErrorKind::FileReadFailed => "FileReadFailed",
135            ErrorKind::FileWriteFailed => "FileWriteFailed",
136            ErrorKind::FileStatFailed => "FileStatFailed",
137            ErrorKind::DirOpenFailed => "DirOpenFailed",
138            ErrorKind::ThreadStackAllocFailed => "ThreadStackAllocFailed",
139            ErrorKind::ThreadSpawnFailed => "ThreadSpawnFailed",
140            ErrorKind::DnsResolveFailed => "DnsResolveFailed",
141            ErrorKind::SocketCreateFailed => "SocketCreateFailed",
142            ErrorKind::SocketConnectFailed => "SocketConnectFailed",
143            ErrorKind::SocketReadFailed => "SocketReadFailed",
144            ErrorKind::SocketWriteFailed => "SocketWriteFailed",
145            ErrorKind::UnexpectedEof => "UnexpectedEof",
146            ErrorKind::ChunkedEofInSize => "ChunkedEofInSize",
147            ErrorKind::ChunkedSizeReadError => "ChunkedSizeReadError",
148            ErrorKind::ChunkedInvalidSize => "ChunkedInvalidSize",
149            ErrorKind::ChunkedDataReadError => "ChunkedDataReadError",
150            ErrorKind::HttpStatusError => "HttpStatusError",
151            ErrorKind::HttpConnectError => "HttpConnectError",
152            ErrorKind::TlsExpectedHandshakeRecord => "TlsExpectedHandshakeRecord",
153            ErrorKind::TlsExpectedServerHello => "TlsExpectedServerHello",
154            ErrorKind::TlsExpectedChangeCipherSpec => "TlsExpectedChangeCipherSpec",
155            ErrorKind::TlsExpectedEncryptedRecords => "TlsExpectedEncryptedRecords",
156            ErrorKind::TlsBadHandshakeFragment => "TlsBadHandshakeFragment",
157            ErrorKind::TlsFinishedVerifyFailed => "TlsFinishedVerifyFailed",
158            ErrorKind::TlsUnsupportedCipher => "TlsUnsupportedCipher",
159            ErrorKind::TlsAlertReceived => "TlsAlertReceived",
160            ErrorKind::TlsRecordTooShort => "TlsRecordTooShort",
161            ErrorKind::TlsHandshakeHeaderTooShort => "TlsHandshakeHeaderTooShort",
162            ErrorKind::TlsHandshakeBodyTooShort => "TlsHandshakeBodyTooShort",
163            ErrorKind::TlsServerHelloTooShort => "TlsServerHelloTooShort",
164            ErrorKind::TlsServerHelloSessionIdInvalid => "TlsServerHelloSessionIdInvalid",
165            ErrorKind::TlsServerHelloExtTooShort => "TlsServerHelloExtTooShort",
166            ErrorKind::TlsExtensionHeaderTooShort => "TlsExtensionHeaderTooShort",
167            ErrorKind::TlsExtensionLengthInvalid => "TlsExtensionLengthInvalid",
168            ErrorKind::TlsKeyShareServerHelloInvalid => "TlsKeyShareServerHelloInvalid",
169            ErrorKind::TlsServerGroupUnsupported => "TlsServerGroupUnsupported",
170            ErrorKind::TlsKeyShareLengthInvalid => "TlsKeyShareLengthInvalid",
171            ErrorKind::TlsServerNotTls13 => "TlsServerNotTls13",
172            ErrorKind::TlsMissingServerKey => "TlsMissingServerKey",
173            ErrorKind::TlsAes128GcmDecryptFailed => "TlsAes128GcmDecryptFailed",
174            ErrorKind::FormatError => "FormatError",
175            ErrorKind::RateLimited => "RateLimited",
176            ErrorKind::Other => "Other",
177        }
178    }
179}
180
181pub type OrtResult<T> = Result<T, OrtError>;
182
183#[derive(Clone, Copy)]
184pub struct OrtError {
185    pub kind: ErrorKind,
186    pub context: &'static str,
187}
188
189pub fn ort_error(kind: ErrorKind, context: &'static str) -> OrtError {
190    OrtError { kind, context }
191}
192
193impl OrtError {
194    pub fn as_string(&self) -> String {
195        let k = self.kind.as_string();
196        let mut out = String::with_capacity(k.len() + 2 + self.context.len());
197        out.push_str(k);
198        out.push_str(": ");
199        out.push_str(self.context);
200        out
201    }
202
203    #[cfg(debug_assertions)]
204    pub fn debug_print(&self) {
205        use crate::{libc, utils::zclean};
206        use alloc::ffi::CString;
207        let mut s = self.as_string();
208        let c_s = CString::new(zclean(&mut s)).unwrap();
209        unsafe {
210            libc::write(2, c_s.as_ptr().cast(), c_s.count_bytes());
211        }
212    }
213
214    #[cfg(not(debug_assertions))]
215    pub fn debug_print(&self) {}
216}
217
218pub trait Context<T, E> {
219    /// Wrap the error value with additional context.
220    fn context(self, context: &'static str) -> Result<T, OrtError>;
221}
222
223impl<T, E> Context<T, E> for Result<T, E>
224where
225    E: Into<OrtError>,
226{
227    /// Wrap the error value with additional context.
228    fn context(self, context: &'static str) -> OrtResult<T> {
229        match self {
230            Ok(ok) => Ok(ok),
231            Err(error) => {
232                let mut err: OrtError = error.into();
233                err.context = context;
234                Err(err)
235            }
236        }
237    }
238}