Skip to main content

ove/
error.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! oveRTOS error types and the [`Result`] alias.
8
9/// oveRTOS error codes, mapped from C `OVE_ERR_*` defines in `ove/types.h`.
10///
11/// When adding a new variant, update [`from_code`](Error::from_code),
12/// [`to_code`](Error::to_code), [`Display`](core::fmt::Display), and
13/// the `_assert_codes_match` function below.
14///
15/// The enum is `#[non_exhaustive]` so the substrate can add new
16/// `OVE_ERR_*` codes (mapped to fresh variants here) without breaking
17/// downstream `match` blocks at compile time.  Callers should always
18/// include a `_ => …` arm.  Matches `std::io::ErrorKind` convention.
19#[non_exhaustive]
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Error {
22    /// The requested resource or subsystem has not been registered (`OVE_ERR_NOT_REGISTERED`).
23    NotRegistered,
24    /// One or more parameters passed to the API were invalid (`OVE_ERR_INVALID_PARAM`).
25    InvalidParam,
26    /// Heap or static allocation failed (`OVE_ERR_NO_MEMORY`).
27    NoMemory,
28    /// The operation did not complete within the allowed time (`OVE_ERR_TIMEOUT`).
29    Timeout,
30    /// The requested operation is not supported by this platform (`OVE_ERR_NOT_SUPPORTED`).
31    NotSupported,
32    /// The queue was full and the item could not be enqueued (`OVE_ERR_QUEUE_FULL`).
33    QueueFull,
34    /// ML inference or model loading failed (`OVE_ERR_ML_FAILED`).
35    MlFailed,
36    /// Remote peer refused the connection (`OVE_ERR_NET_REFUSED`).
37    NetRefused,
38    /// Network or host is unreachable (`OVE_ERR_NET_UNREACHABLE`).
39    NetUnreachable,
40    /// Local address already in use (`OVE_ERR_NET_ADDR_IN_USE`).
41    NetAddrInUse,
42    /// Connection was reset by the remote peer (`OVE_ERR_NET_RESET`).
43    NetReset,
44    /// DNS name resolution failed (`OVE_ERR_NET_DNS_FAIL`).
45    NetDnsFail,
46    /// Connection closed by the remote peer (`OVE_ERR_NET_CLOSED`).
47    NetClosed,
48    /// Bus peripheral: device did not acknowledge (`OVE_ERR_BUS_NACK`).
49    BusNack,
50    /// Bus peripheral: bus is busy / arbitration lost (`OVE_ERR_BUS_BUSY`).
51    BusBusy,
52    /// Bus peripheral: generic bus error (`OVE_ERR_BUS_ERROR`).
53    BusError,
54    /// The queue was empty and no item could be received (`OVE_ERR_QUEUE_EMPTY`).
55    QueueEmpty,
56    /// A non-blocking operation would have had to block (`OVE_ERR_WOULD_BLOCK`).
57    WouldBlock,
58    /// End of file / directory iterator exhausted (`OVE_ERR_EOF`).
59    Eof,
60    /// Argument or state is invalid for this operation (`OVE_ERR_INVAL`).
61    Inval,
62    /// Requested key / entry / resource was not found (`OVE_ERR_NOT_FOUND`).
63    NotFound,
64    /// An error code not covered by the above variants; the raw code is preserved.
65    Unknown(i32),
66}
67
68/// Convenience alias for `core::result::Result<T, Error>`.
69pub type Result<T> = core::result::Result<T, Error>;
70
71impl Error {
72    /// Convert a C return code to `Result<()>`.
73    /// Zero (OVE_OK) maps to `Ok(())`, negative values map to the
74    /// corresponding `Error` variant.
75    #[inline]
76    pub fn from_code(code: i32) -> Result<()> {
77        match code {
78            0 => Ok(()),
79            -1 => Err(Error::NotRegistered),
80            -2 => Err(Error::InvalidParam),
81            -3 => Err(Error::NoMemory),
82            -4 => Err(Error::Timeout),
83            -5 => Err(Error::NotSupported),
84            -6 => Err(Error::QueueFull),
85            -7 => Err(Error::MlFailed),
86            -8 => Err(Error::NetRefused),
87            -9 => Err(Error::NetUnreachable),
88            -10 => Err(Error::NetAddrInUse),
89            -11 => Err(Error::NetReset),
90            -12 => Err(Error::NetDnsFail),
91            -13 => Err(Error::NetClosed),
92            -14 => Err(Error::BusNack),
93            -15 => Err(Error::BusBusy),
94            -16 => Err(Error::BusError),
95            -17 => Err(Error::QueueEmpty),
96            -18 => Err(Error::WouldBlock),
97            -19 => Err(Error::Eof),
98            -20 => Err(Error::Inval),
99            -21 => Err(Error::NotFound),
100            other => Err(Error::Unknown(other)),
101        }
102    }
103
104    /// Convert back to the raw C error code.
105    #[inline]
106    pub fn to_code(self) -> i32 {
107        match self {
108            Error::NotRegistered => -1,
109            Error::InvalidParam => -2,
110            Error::NoMemory => -3,
111            Error::Timeout => -4,
112            Error::NotSupported => -5,
113            Error::QueueFull => -6,
114            Error::MlFailed => -7,
115            Error::NetRefused => -8,
116            Error::NetUnreachable => -9,
117            Error::NetAddrInUse => -10,
118            Error::NetReset => -11,
119            Error::NetDnsFail => -12,
120            Error::NetClosed => -13,
121            Error::BusNack => -14,
122            Error::BusBusy => -15,
123            Error::BusError => -16,
124            Error::QueueEmpty => -17,
125            Error::WouldBlock => -18,
126            Error::Eof => -19,
127            Error::Inval => -20,
128            Error::NotFound => -21,
129            Error::Unknown(c) => c,
130        }
131    }
132
133    /// Returns `true` if this is a networking-related error.
134    #[inline]
135    pub fn is_net_error(&self) -> bool {
136        matches!(
137            self,
138            Error::NetRefused
139                | Error::NetUnreachable
140                | Error::NetAddrInUse
141                | Error::NetReset
142                | Error::NetDnsFail
143                | Error::NetClosed
144        )
145    }
146
147    /// Returns `true` if this is a bus peripheral error (I2C/SPI).
148    #[inline]
149    pub fn is_bus_error(&self) -> bool {
150        matches!(self, Error::BusNack | Error::BusBusy | Error::BusError)
151    }
152}
153
154// Compile-time assertion: verify Rust error codes match the C `OVE_ERR_*`
155// defines from `ove/types.h`.  If the C header changes its numbering,
156// bindgen will regenerate different constants and this function will fail
157// to compile.
158#[cfg(not(docsrs))]
159const fn _assert_codes_match() {
160    use crate::bindings;
161    assert!(bindings::OVE_ERR_NOT_REGISTERED == -1);
162    assert!(bindings::OVE_ERR_INVALID_PARAM == -2);
163    assert!(bindings::OVE_ERR_NO_MEMORY == -3);
164    assert!(bindings::OVE_ERR_TIMEOUT == -4);
165    assert!(bindings::OVE_ERR_NOT_SUPPORTED == -5);
166    assert!(bindings::OVE_ERR_QUEUE_FULL == -6);
167    assert!(bindings::OVE_ERR_ML_FAILED == -7);
168    assert!(bindings::OVE_ERR_NET_REFUSED == -8);
169    assert!(bindings::OVE_ERR_NET_UNREACHABLE == -9);
170    assert!(bindings::OVE_ERR_NET_ADDR_IN_USE == -10);
171    assert!(bindings::OVE_ERR_NET_RESET == -11);
172    assert!(bindings::OVE_ERR_NET_DNS_FAIL == -12);
173    assert!(bindings::OVE_ERR_NET_CLOSED == -13);
174    assert!(bindings::OVE_ERR_BUS_NACK == -14);
175    assert!(bindings::OVE_ERR_BUS_BUSY == -15);
176    assert!(bindings::OVE_ERR_BUS_ERROR == -16);
177    assert!(bindings::OVE_ERR_QUEUE_EMPTY == -17);
178    assert!(bindings::OVE_ERR_WOULD_BLOCK == -18);
179    assert!(bindings::OVE_ERR_EOF == -19);
180    assert!(bindings::OVE_ERR_INVAL == -20);
181    assert!(bindings::OVE_ERR_NOT_FOUND == -21);
182}
183
184#[cfg(not(docsrs))]
185#[allow(clippy::used_underscore_items)] // intentional compile-time assertion
186const _: () = _assert_codes_match();
187
188/// `core::error::Error` is stable since Rust 1.81; our MSRV is 1.85 so
189/// the impl can be unconditional.  `std::error::Error` is a re-export
190/// of `core::error::Error` (also since 1.81), so this single `impl`
191/// covers both `no_std` and `std` consumers — no `#[cfg(feature = "std")]`
192/// gymnastics.  The default `source() -> None` is correct: `ove::Error`
193/// doesn't chain to a lower-level cause.
194impl core::error::Error for Error {}
195
196impl core::fmt::Display for Error {
197    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
198        match self {
199            Error::NotRegistered => write!(f, "not registered"),
200            Error::InvalidParam => write!(f, "invalid parameter"),
201            Error::NoMemory => write!(f, "out of memory"),
202            Error::Timeout => write!(f, "timeout"),
203            Error::NotSupported => write!(f, "not supported"),
204            Error::QueueFull => write!(f, "queue full"),
205            Error::MlFailed => write!(f, "ML inference failed"),
206            Error::NetRefused => write!(f, "connection refused"),
207            Error::NetUnreachable => write!(f, "network unreachable"),
208            Error::NetAddrInUse => write!(f, "address in use"),
209            Error::NetReset => write!(f, "connection reset"),
210            Error::NetDnsFail => write!(f, "DNS resolution failed"),
211            Error::NetClosed => write!(f, "connection closed"),
212            Error::BusNack => write!(f, "bus NACK"),
213            Error::BusBusy => write!(f, "bus busy"),
214            Error::BusError => write!(f, "bus error"),
215            Error::QueueEmpty => write!(f, "queue empty"),
216            Error::WouldBlock => write!(f, "would block"),
217            Error::Eof => write!(f, "end of file"),
218            Error::Inval => write!(f, "invalid argument"),
219            Error::NotFound => write!(f, "not found"),
220            Error::Unknown(c) => write!(f, "unknown error ({c})"),
221        }
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    const KNOWN: &[(i32, Error)] = &[
230        (-1, Error::NotRegistered),
231        (-2, Error::InvalidParam),
232        (-3, Error::NoMemory),
233        (-4, Error::Timeout),
234        (-5, Error::NotSupported),
235        (-6, Error::QueueFull),
236        (-7, Error::MlFailed),
237        (-8, Error::NetRefused),
238        (-9, Error::NetUnreachable),
239        (-10, Error::NetAddrInUse),
240        (-11, Error::NetReset),
241        (-12, Error::NetDnsFail),
242        (-13, Error::NetClosed),
243        (-14, Error::BusNack),
244        (-15, Error::BusBusy),
245        (-16, Error::BusError),
246        (-17, Error::QueueEmpty),
247        (-18, Error::WouldBlock),
248        (-19, Error::Eof),
249        (-20, Error::Inval),
250        (-21, Error::NotFound),
251    ];
252
253    #[test]
254    fn from_code_zero_is_ok() {
255        assert_eq!(Error::from_code(0), Ok(()));
256    }
257
258    #[test]
259    fn from_code_known_codes_round_trip_through_to_code() {
260        for &(code, expected) in KNOWN {
261            assert_eq!(Error::from_code(code), Err(expected));
262            assert_eq!(expected.to_code(), code);
263        }
264    }
265
266    #[test]
267    fn from_code_unknown_preserves_raw_code() {
268        assert_eq!(Error::from_code(-999), Err(Error::Unknown(-999)));
269        assert_eq!(Error::Unknown(-999).to_code(), -999);
270        // Positive codes are also "Unknown" (the API contract is
271        // negative-on-error, zero-on-ok; positive should not appear, but
272        // preserving the raw code keeps round-tripping lossless).
273        assert_eq!(Error::from_code(7), Err(Error::Unknown(7)));
274    }
275
276    #[test]
277    fn classifier_predicates() {
278        assert!(Error::NetRefused.is_net_error());
279        assert!(Error::NetClosed.is_net_error());
280        assert!(!Error::Timeout.is_net_error());
281        assert!(!Error::Unknown(-99).is_net_error());
282
283        assert!(Error::BusNack.is_bus_error());
284        assert!(Error::BusError.is_bus_error());
285        assert!(!Error::Timeout.is_bus_error());
286        assert!(!Error::NetRefused.is_bus_error());
287    }
288}