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