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