1use 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 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}