Skip to main content

razor_stream/
error.rs

1//! Error handling for razor-stream RPC framework.
2//!
3//! This module provides the error types and traits used for RPC error handling.
4//! It supports both internal RPC errors and user-defined custom errors.
5//!
6//! # Default Supported Error Types
7//!
8//! The following types have built-in `RpcErrCodec` implementations:
9//!
10//! - **Numeric types**: `i8`, `u8`, `i16`, `u16`, `i32`, `u32`
11//!   - Encoded as `u32` values for efficient transport
12//!   - Useful for errno-style error codes
13//!
14//! - **`()` (unit type)**
15//!   - Encoded as `0u32`
16//!   - Used when no additional error information is needed
17//!
18//! - **`String`**
19//!   - Encoded as UTF-8 bytes
20//!   - Useful for descriptive error messages
21//!
22//! - **`nix::errno::Errno`**
23//!   - Encoded as `u32` values
24//!   - For system-level error codes
25//!
26//! # Custom Error Types
27//!
28//! To use your own error type in RPC methods, implement the [`RpcErrCodec`] trait.
29//! There are two common approaches:
30//!
31//! ## Approach 1: Numeric Encoding (errno-style)
32//!
33//! Use this when you want compact, efficient numeric error codes.
34//!
35//! ```rust
36//! use num_enum::TryFromPrimitive;
37//! use razor_stream::{Codec, error::{RpcErrCodec, RpcIntErr, EncodedErr}};
38//!
39//! #[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
40//! #[repr(u32)]
41//! pub enum MyErrorCode {
42//!     NotFound = 1,
43//!     PermissionDenied = 2,
44//!     Timeout = 3,
45//! }
46//!
47//! impl RpcErrCodec for MyErrorCode {
48//!     fn encode<C: Codec>(&self, _codec: &C) -> EncodedErr {
49//!         EncodedErr::Num(*self as u32)
50//!     }
51//!
52//!     fn decode<C: Codec>(_codec: &C, buf: Result<u32, &[u8]>) -> Result<Self, ()> {
53//!         if let Ok(code) = buf {
54//!             return MyErrorCode::try_from(code).map_err(|_| ());
55//!         }
56//!         Err(())
57//!     }
58//!
59//!     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
60//!         std::fmt::Debug::fmt(self, f)
61//!     }
62//! }
63//! ```
64//!
65//! ## Approach 2: String Encoding (with strum)
66//!
67//! Use this when you want human-readable error strings that can be serialized/deserialized.
68//! Uses `EncodedErr::Static` to avoid heap allocation during encoding.
69//!
70//! ```rust
71//! use razor_stream::{Codec, error::{RpcErrCodec, RpcIntErr, EncodedErr}};
72//! use std::str::FromStr;
73//! use strum::{Display, EnumString, IntoStaticStr};
74//!
75//! #[derive(Debug, Clone, Display, EnumString, IntoStaticStr, PartialEq)]
76//! pub enum MyStringError {
77//!     #[strum(serialize = "not_found")]
78//!     NotFound,
79//!     #[strum(serialize = "permission_denied")]
80//!     PermissionDenied,
81//!     #[strum(serialize = "timeout")]
82//!     Timeout,
83//! }
84//!
85//! impl RpcErrCodec for MyStringError {
86//!     fn encode<C: Codec>(&self, _codec: &C) -> EncodedErr {
87//!         // Use EncodedErr::Static to avoid heap allocation, with the help of strum::IntoStaticStr
88//!         EncodedErr::Static(self.into())
89//!     }
90//!
91//!     fn decode<C: Codec>(_codec: &C, buf: Result<u32, &[u8]>) -> Result<Self, ()> {
92//!         // Decode with zero-copy: directly parse from &[u8] without allocating
93//!         if let Err(bytes) = buf {
94//!             // Safety: error strings are valid ASCII (subset of UTF-8), so unchecked is safe
95//!             let s = unsafe { std::str::from_utf8_unchecked(bytes) };
96//!             // Use strum's EnumString derive to parse from string
97//!             return MyStringError::from_str(s).map_err(|_| ());
98//!         }
99//!         Err(())
100//!     }
101//!
102//!     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
103//!         std::fmt::Display::fmt(self, f)
104//!     }
105//! }
106//! ```
107
108use crate::Codec;
109use std::fmt;
110
111/// "rpc_" prefix is reserved for internal error, you should avoid conflict with it
112pub const RPC_ERR_PREFIX: &str = "rpc_";
113
114/// A error type defined by client-side user logic
115///
116/// Due to possible decode
117#[derive(thiserror::Error)]
118pub enum RpcError<E: RpcErrCodec> {
119    User(#[from] E),
120    Rpc(#[from] RpcIntErr),
121}
122
123impl<E: RpcErrCodec> fmt::Display for RpcError<E> {
124    #[inline]
125    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126        match self {
127            Self::User(e) => RpcErrCodec::fmt(e, f),
128            Self::Rpc(e) => fmt::Display::fmt(e, f),
129        }
130    }
131}
132
133impl<E: RpcErrCodec> fmt::Debug for RpcError<E> {
134    #[inline]
135    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
136        fmt::Display::fmt(self, f)
137    }
138}
139
140impl<E: RpcErrCodec> std::cmp::PartialEq<RpcIntErr> for RpcError<E> {
141    #[inline]
142    fn eq(&self, other: &RpcIntErr) -> bool {
143        if let Self::Rpc(r) = self
144            && r == other
145        {
146            return true;
147        }
148        false
149    }
150}
151
152impl<E: RpcErrCodec + PartialEq> std::cmp::PartialEq<E> for RpcError<E> {
153    #[inline]
154    fn eq(&self, other: &E) -> bool {
155        if let Self::User(r) = self {
156            return r == other;
157        }
158        false
159    }
160}
161
162impl<E: RpcErrCodec + PartialEq> std::cmp::PartialEq<RpcError<E>> for RpcError<E> {
163    #[inline]
164    fn eq(&self, other: &Self) -> bool {
165        match self {
166            Self::Rpc(r) => {
167                if let Self::Rpc(o) = other {
168                    return r == o;
169                }
170            }
171            Self::User(r) => {
172                if let Self::User(o) = other {
173                    return r == o;
174                }
175            }
176        }
177        false
178    }
179}
180
181impl From<&str> for RpcError<String> {
182    #[inline]
183    fn from(e: &str) -> Self {
184        Self::User(e.to_string())
185    }
186}
187
188/// Serialize and Deserialize trait for custom RpcError
189///
190/// There is only two forms for rpc transport layer, u32 and String, choose one of them.
191///
192/// Because Rust does not allow overlapping impl, we only imple RpcErrCodec trait by default for the following types:
193/// - ()
194/// - from i8 to u32
195/// - String
196/// - nix::errno::Errno
197///
198/// If you use other type as error, you can implement manually:
199///
200/// # Example with serde_derive
201/// ```rust
202/// use serde_derive::{Serialize, Deserialize};
203/// use razor_stream::{Codec, error::{RpcErrCodec, RpcIntErr, EncodedErr}};
204/// use strum::Display;
205/// #[derive(Serialize, Deserialize, Debug)]
206/// pub enum MyError {
207///     NoSuchFile,
208///     TooManyRequest,
209/// }
210///
211/// impl RpcErrCodec for MyError {
212///     #[inline(always)]
213///     fn encode<C: Codec>(&self, codec: &C) -> EncodedErr {
214///         match codec.encode(self) {
215///             Ok(buf)=>EncodedErr::Buf(buf),
216///             Err(())=>EncodedErr::Rpc(RpcIntErr::Encode),
217///         }
218///     }
219///
220///     #[inline(always)]
221///     fn decode<C: Codec>(codec: &C, buf: Result<u32, &[u8]>) -> Result<Self, ()> {
222///         if let Err(b) = buf {
223///             return codec.decode(b);
224///         } else {
225///             Err(())
226///         }
227///     }
228///     #[inline(always)]
229///     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
230///         std::fmt::Debug::fmt(self, f)
231///     }
232/// }
233/// ```
234///
235/// # Example with num_enum
236///
237/// ```rust
238/// use num_enum::TryFromPrimitive;
239/// use razor_stream::{Codec, error::{RpcErrCodec, RpcIntErr, EncodedErr}};
240///
241/// // Define your error codes as a C-like enum with explicit values
242/// // You can use num_enum's TryFromPrimitive for safer deserialization
243/// #[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
244/// #[repr(u32)]
245/// pub enum MyRpcErrorCode {
246///     /// Service is not available
247///     ServiceUnavailable = 1,
248///     /// Request timed out
249///     RequestTimeout = 2,
250///     /// Invalid parameter
251///     InvalidParameter = 3,
252///     /// Resource not found
253///     NotFound = 4,
254/// }
255///
256/// impl RpcErrCodec for MyRpcErrorCode {
257///     #[inline(always)]
258///     fn encode<C: Codec>(&self, _codec: &C) -> EncodedErr {
259///         // Manual conversion to u32 (no IntoPrimitive needed)
260///         let code: u32 = *self as u32;
261///         EncodedErr::Num(code)
262///     }
263///
264///     #[inline(always)]
265///     fn decode<C: Codec>(_codec: &C, buf: Result<u32, &[u8]>) -> Result<Self, ()> {
266///         if let Ok(code) = buf {
267///             // Using num_enum for safe deserialization (TryFromPrimitive)
268///             return MyRpcErrorCode::try_from(code).map_err(|_| ());
269///         }
270///         Err(())
271///     }
272///
273///     #[inline(always)]
274///     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
275///         std::fmt::Debug::fmt(self, f)
276///     }
277/// }
278/// ```
279pub trait RpcErrCodec: Send + Sized + 'static + Unpin {
280    fn encode<C: Codec>(&self, codec: &C) -> EncodedErr;
281
282    fn decode<C: Codec>(codec: &C, buf: Result<u32, &[u8]>) -> Result<Self, ()>;
283
284    /// You can choose to use std::fmt::Debug or std::fmt::Display for the type.
285    ///
286    /// NOTE that this method exists because rust does not have Display for ().
287    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
288}
289
290macro_rules! impl_rpc_error_for_num {
291    ($t: tt) => {
292        impl RpcErrCodec for $t {
293            #[inline(always)]
294            fn encode<C: Codec>(&self, _codec: &C) -> EncodedErr {
295                EncodedErr::Num(*self as u32)
296            }
297
298            #[inline(always)]
299            fn decode<C: Codec>(_codec: &C, buf: Result<u32, &[u8]>) -> Result<Self, ()> {
300                if let Ok(i) = buf {
301                    if i <= $t::MAX as u32 {
302                        return Ok(i as Self);
303                    }
304                }
305                Err(())
306            }
307
308            #[inline(always)]
309            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
310                write!(f, "errno {}", self)
311            }
312        }
313    };
314}
315
316impl_rpc_error_for_num!(i8);
317impl_rpc_error_for_num!(u8);
318impl_rpc_error_for_num!(i16);
319impl_rpc_error_for_num!(u16);
320impl_rpc_error_for_num!(i32);
321impl_rpc_error_for_num!(u32);
322
323impl RpcErrCodec for nix::errno::Errno {
324    #[inline(always)]
325    fn encode<C: Codec>(&self, _codec: &C) -> EncodedErr {
326        EncodedErr::Num(*self as u32)
327    }
328
329    #[inline(always)]
330    fn decode<C: Codec>(_codec: &C, buf: Result<u32, &[u8]>) -> Result<Self, ()> {
331        if let Ok(i) = buf
332            && i <= i32::MAX as u32
333        {
334            return Ok(Self::from_raw(i as i32));
335        }
336        Err(())
337    }
338
339    #[inline(always)]
340    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
341        write!(f, "{:?}", self)
342    }
343}
344
345impl RpcErrCodec for () {
346    #[inline(always)]
347    fn encode<C: Codec>(&self, _codec: &C) -> EncodedErr {
348        EncodedErr::Num(0u32)
349    }
350
351    #[inline(always)]
352    fn decode<C: Codec>(_codec: &C, buf: Result<u32, &[u8]>) -> Result<Self, ()> {
353        if let Ok(i) = buf
354            && i == 0
355        {
356            return Ok(());
357        }
358        Err(())
359    }
360
361    #[inline(always)]
362    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
363        write!(f, "err")
364    }
365}
366
367impl RpcErrCodec for String {
368    #[inline(always)]
369    fn encode<C: Codec>(&self, _codec: &C) -> EncodedErr {
370        EncodedErr::Buf(Vec::from(self.as_bytes()))
371    }
372    #[inline(always)]
373    fn decode<C: Codec>(_codec: &C, buf: Result<u32, &[u8]>) -> Result<Self, ()> {
374        if let Err(s) = buf
375            && let Ok(s) = str::from_utf8(s)
376        {
377            return Ok(s.to_string());
378        }
379        Err(())
380    }
381
382    #[inline(always)]
383    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
384        write!(f, "{}", self)
385    }
386}
387
388/// RpcIntErr represent internal error from the framework
389///
390/// **NOTE**:
391/// - This error type is serialized in string, "rpc_" prefix is reserved for internal error, you
392///   should avoid conflict with it.
393/// - We presume the variants less than RpcIntErr::Method is retriable errors
394#[derive(
395    strum::Display,
396    strum::EnumString,
397    strum::AsRefStr,
398    PartialEq,
399    PartialOrd,
400    Clone,
401    thiserror::Error,
402)]
403#[repr(u8)]
404pub enum RpcIntErr {
405    /// Ping or connect error
406    #[strum(serialize = "rpc_unreachable")]
407    Unreachable = 0,
408    /// IO error
409    #[strum(serialize = "rpc_io_err")]
410    IO = 1,
411    /// Task timeout
412    #[strum(serialize = "rpc_timeout")]
413    Timeout = 2,
414    /// Method not found
415    #[strum(serialize = "rpc_method_notfound")]
416    Method = 3,
417    /// service notfound
418    #[strum(serialize = "rpc_service_notfound")]
419    Service = 4,
420    /// Encode Error
421    #[strum(serialize = "rpc_encode")]
422    Encode = 5,
423    /// Decode Error
424    #[strum(serialize = "rpc_decode")]
425    Decode = 6,
426    /// Internal error
427    #[strum(serialize = "rpc_internal_err")]
428    Internal = 7,
429    /// invalid version number in rpc header
430    #[strum(serialize = "rpc_invalid_ver")]
431    Version = 8,
432}
433
434// The default Debug derive just ignore strum customized string, by strum only have a Display derive
435impl fmt::Debug for RpcIntErr {
436    #[inline]
437    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
438        fmt::Display::fmt(self, f)
439    }
440}
441
442impl RpcIntErr {
443    #[inline]
444    pub fn as_bytes(&self) -> &[u8] {
445        self.as_ref().as_bytes()
446    }
447}
448
449impl From<std::io::Error> for RpcIntErr {
450    #[inline(always)]
451    fn from(_e: std::io::Error) -> Self {
452        Self::IO
453    }
454}
455
456/// A container for error message parse from / send into transport
457#[derive(Debug)]
458pub enum EncodedErr {
459    /// The ClientTransport should try the best to parse it from string with "rpc_" prefix
460    Rpc(RpcIntErr),
461    /// For nix errno and the like
462    Num(u32),
463    /// Only for server-side to encode err.
464    ///
465    /// The ClientTransport cannot decode into static type
466    Static(&'static str),
467    /// The ClientTransport will fallback to `Vec<u8>` after try to parse  RpcIntErr and  num
468    Buf(Vec<u8>),
469}
470
471impl EncodedErr {
472    #[inline]
473    pub fn try_as_str(&self) -> Result<&str, ()> {
474        match self {
475            Self::Static(s) => return Ok(s),
476            Self::Buf(b) => {
477                if let Ok(s) = str::from_utf8(b) {
478                    return Ok(s);
479                }
480            }
481            _ => {}
482        }
483        Err(())
484    }
485}
486
487/// Just for macro test
488impl std::cmp::PartialEq<EncodedErr> for EncodedErr {
489    fn eq(&self, other: &EncodedErr) -> bool {
490        match self {
491            Self::Rpc(e) => {
492                if let Self::Rpc(o) = other {
493                    return e == o;
494                }
495            }
496            Self::Num(e) => {
497                if let Self::Num(o) = other {
498                    return e == o;
499                }
500            }
501            Self::Static(s) => {
502                if let Ok(o) = other.try_as_str() {
503                    return *s == o;
504                }
505            }
506            Self::Buf(s) => {
507                if let Self::Buf(o) = other {
508                    return s == o;
509                } else if let Ok(o) = other.try_as_str() {
510                    // other's type is not Buf
511                    if let Ok(_s) = str::from_utf8(s) {
512                        return _s == o;
513                    }
514                }
515            }
516        }
517        false
518    }
519}
520
521impl fmt::Display for EncodedErr {
522    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523        match self {
524            Self::Rpc(e) => e.fmt(f),
525            Self::Num(no) => write!(f, "errno {}", no),
526            Self::Static(s) => write!(f, "{}", s),
527            Self::Buf(b) => match str::from_utf8(b) {
528                Ok(s) => {
529                    write!(f, "{}", s)
530                }
531                Err(_) => {
532                    write!(f, "err blob {} length", b.len())
533                }
534            },
535        }
536    }
537}
538
539impl From<RpcIntErr> for EncodedErr {
540    #[inline(always)]
541    fn from(e: RpcIntErr) -> Self {
542        Self::Rpc(e)
543    }
544}
545
546#[cfg(test)]
547mod tests {
548    use super::*;
549    use nix::errno::Errno;
550    use std::str::FromStr;
551
552    #[test]
553    fn test_internal_error() {
554        println!("{}", RpcIntErr::Internal);
555        println!("{:?}", RpcIntErr::Internal);
556        let s = RpcIntErr::Timeout.as_ref();
557        println!("RpcIntErr::Timeout as {}", s);
558        let e = RpcIntErr::from_str(s).expect("parse");
559        assert_eq!(e, RpcIntErr::Timeout);
560        assert!(RpcIntErr::from_str("timeoutss").is_err());
561        assert!(RpcIntErr::Timeout < RpcIntErr::Method);
562        assert!(RpcIntErr::IO < RpcIntErr::Method);
563        assert!(RpcIntErr::Unreachable < RpcIntErr::Method);
564    }
565
566    #[test]
567    fn test_rpc_error_default() {
568        let e = RpcError::<i32>::from(1i32);
569        println!("err {:?} {}", e, e);
570
571        let e = RpcError::<Errno>::from(Errno::EIO);
572        println!("err {:?} {}", e, e);
573
574        let e = RpcError::<String>::from("err_str");
575        println!("err {:?} {}", e, e);
576        let e2 = RpcError::<String>::from("err_str".to_string());
577        assert_eq!(e, e2);
578
579        let e = RpcError::<String>::from(RpcIntErr::IO);
580        println!("err {:?} {}", e, e);
581
582        let _e: Result<(), RpcIntErr> = Err(RpcIntErr::IO);
583
584        // let e: Result<(), RpcError::<String>> = _e.into();
585        // Not allow by rust, and orphan rule prevent we do
586        // `impl<E: RpcErrCodec> From<Result<(), RpcIntErr>> for Result<(), RpcError<E>>`
587
588        // it's ok to use map_err
589        let e: Result<(), RpcError<String>> = _e.map_err(|e| e.into());
590        println!("err {:?}", e);
591    }
592
593    //#[test]
594    //fn test_rpc_error_string_enum() {
595    //    not supported by default, should provide a derive
596    //    #[derive(
597    //        Debug, strum::Display, strum::EnumString, strum::AsRefStr, PartialEq, Clone, thiserror::Error,
598    //    )]
599    //    enum MyError {
600    //        OhMyGod,
601    //    }
602    //    let e = RpcError::<MyError>::from(MyError::OhMyGod);
603    //    println!("err {:?} {}", e, e);
604    //}
605}