Skip to main content

aerro/
remote.rs

1//! Type-erased fallback for unknown wire types — see spec §5.
2//!
3//! Like [`crate::ServiceFailure`], the actual state lives behind a single
4//! `Box` so `Result<_, RemoteError>` stays pointer-sized.
5
6use bytes::Bytes;
7use smallvec::SmallVec;
8use tonic::Code;
9
10use crate::{Aerro, Category, Frame, trace::TraceContext};
11
12#[derive(Debug)]
13pub struct RemoteErrorInner {
14    pub(crate) category: Category,
15    pub(crate) type_id: String,
16    pub(crate) frames: SmallVec<[Frame; 4]>,
17    pub(crate) trace: TraceContext,
18    pub(crate) outer_code: Code,
19    pub(crate) outer_message: String,
20    pub(crate) payload_bytes: Bytes,
21}
22
23#[derive(Debug)]
24pub struct RemoteError {
25    state: Box<RemoteErrorInner>,
26}
27
28impl RemoteError {
29    pub fn from_parts(inner: RemoteErrorInner) -> Self {
30        Self {
31            state: Box::new(inner),
32        }
33    }
34
35    /// Recover a typed variant whose `type_id` is in `E::TYPE_IDS`.
36    pub fn downcast<E: Aerro>(&self) -> Option<E> {
37        if !E::TYPE_IDS.contains(&self.state.type_id.as_str()) {
38            return None;
39        }
40        E::decode_payload(&self.state.type_id, &self.state.payload_bytes).ok()
41    }
42
43    pub fn category(&self) -> Category {
44        self.state.category
45    }
46
47    pub fn type_id(&self) -> &str {
48        &self.state.type_id
49    }
50
51    pub fn frames(&self) -> &SmallVec<[Frame; 4]> {
52        &self.state.frames
53    }
54
55    pub fn trace(&self) -> &TraceContext {
56        &self.state.trace
57    }
58
59    pub fn outer_code(&self) -> Code {
60        self.state.outer_code
61    }
62
63    pub fn outer_message(&self) -> &str {
64        &self.state.outer_message
65    }
66}
67
68impl std::fmt::Display for RemoteError {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        write!(f, "{}: {}", self.state.outer_code, self.state.outer_message)
71    }
72}
73
74impl std::error::Error for RemoteError {}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::test_support::Boom;
80
81    fn make(inner: RemoteErrorInner) -> RemoteError {
82        RemoteError::from_parts(inner)
83    }
84
85    #[test]
86    fn downcast_recovers_known_type() {
87        use crate::Exposure;
88        let mut buf = Vec::new();
89        Boom { x: 7 }
90            .encode_payload(Exposure::Internal, &mut buf)
91            .unwrap();
92        let r = make(RemoteErrorInner {
93            category: Category::System,
94            type_id: "toy.boom".into(),
95            frames: SmallVec::new(),
96            trace: TraceContext::default(),
97            outer_code: Code::Internal,
98            outer_message: "toy.boom".into(),
99            payload_bytes: Bytes::from(buf),
100        });
101        assert_eq!(r.downcast::<Boom>().unwrap().x, 7);
102    }
103
104    #[test]
105    fn downcast_returns_none_on_mismatch() {
106        let r = make(RemoteErrorInner {
107            category: Category::Business,
108            type_id: "other".into(),
109            frames: SmallVec::new(),
110            trace: TraceContext::default(),
111            outer_code: Code::NotFound,
112            outer_message: "x".into(),
113            payload_bytes: Bytes::new(),
114        });
115        assert!(r.downcast::<Boom>().is_none());
116    }
117
118    #[test]
119    fn remote_error_is_pointer_sized() {
120        assert_eq!(
121            std::mem::size_of::<RemoteError>(),
122            std::mem::size_of::<usize>()
123        );
124    }
125}