oasis_runtime_sdk/
error.rs

1//! Error types for runtimes.
2pub use oasis_core_runtime::types::Error as RuntimeError;
3
4use crate::{dispatcher, module::CallResult};
5
6/// A runtime error that gets propagated to the caller.
7///
8/// It extends `std::error::Error` with module name and error code so that errors can be easily
9/// serialized and transferred between different processes.
10///
11/// This trait can be derived:
12/// ```
13/// # #[cfg(feature = "oasis-runtime-sdk-macros")]
14/// # mod example {
15/// # use oasis_runtime_sdk_macros::Error;
16/// const MODULE_NAME: &str = "my-module";
17/// #[derive(Clone, Debug, Error, thiserror::Error)]
18/// #[sdk_error(autonumber)] // `module_name` meta is required if `MODULE_NAME` isn't in scope
19/// enum Error {
20///    #[error("invalid argument")]
21///    InvalidArgument,          // autonumbered to 0
22///
23///    #[error("forbidden")]
24///    #[sdk_error(code = 401)]  // manually numbered to 403 (`code` or autonumbering is required)
25///    Forbidden,
26/// }
27/// # }
28/// ```
29pub trait Error: std::error::Error {
30    /// Name of the module that emitted the error.
31    fn module_name(&self) -> &str;
32
33    /// Error code uniquely identifying the error.
34    fn code(&self) -> u32;
35
36    /// Converts the error into a call result.
37    fn into_call_result(self) -> CallResult
38    where
39        Self: Sized,
40    {
41        match self.into_abort() {
42            Ok(err) => CallResult::Aborted(err),
43            Err(failed) => CallResult::Failed {
44                module: failed.module_name().to_owned(),
45                code: failed.code(),
46                message: failed.to_string(),
47            },
48        }
49    }
50
51    /// Consumes self and returns either `Ok(err)` (where `err` is a dispatcher error) when batch
52    /// should abort or `Err(self)` when this is just a regular error.
53    fn into_abort(self) -> Result<dispatcher::Error, Self>
54    where
55        Self: Sized,
56    {
57        Err(self)
58    }
59}
60
61impl Error for std::convert::Infallible {
62    fn module_name(&self) -> &str {
63        "(none)"
64    }
65
66    fn code(&self) -> u32 {
67        Default::default()
68    }
69}
70
71#[cfg(test)]
72mod test {
73    use super::*;
74
75    const MODULE_NAME_1: &str = "test1";
76    const MODULE_NAME_2: &str = "test2";
77
78    #[derive(thiserror::Error, Debug, oasis_runtime_sdk_macros::Error)]
79    #[sdk_error(module_name = "MODULE_NAME_1")]
80    enum ChildError {
81        #[error("first error")]
82        #[sdk_error(code = 1)]
83        Error1,
84
85        #[error("second error")]
86        #[sdk_error(code = 2)]
87        Error2,
88    }
89
90    #[derive(thiserror::Error, Debug, oasis_runtime_sdk_macros::Error)]
91    #[sdk_error(module_name = "MODULE_NAME_2")]
92    enum ParentError {
93        #[error("first error")]
94        #[sdk_error(code = 1)]
95        NotForwarded(#[source] ChildError),
96
97        #[error("nested error")]
98        #[sdk_error(transparent)]
99        Nested(#[source] ChildError),
100    }
101
102    #[derive(thiserror::Error, Debug, oasis_runtime_sdk_macros::Error)]
103    enum ParentParentError {
104        #[error("nested nested error")]
105        #[sdk_error(transparent)]
106        Nested(#[source] ParentError),
107    }
108
109    #[test]
110    fn test_error_sources_1() {
111        let err = ParentError::Nested(ChildError::Error1);
112        let result = err.into_call_result();
113
114        match result {
115            CallResult::Failed {
116                module,
117                code,
118                message: _,
119            } => {
120                assert_eq!(module, "test1");
121                assert_eq!(code, 1);
122            }
123            _ => panic!("expected failed result, got: {:?}", result),
124        }
125
126        let err = ParentError::Nested(ChildError::Error2);
127        let result = err.into_call_result();
128
129        match result {
130            CallResult::Failed {
131                module,
132                code,
133                message: _,
134            } => {
135                assert_eq!(module, "test1");
136                assert_eq!(code, 2);
137            }
138            _ => panic!("expected failed result, got: {:?}", result),
139        }
140    }
141
142    #[test]
143    fn test_error_sources_2() {
144        let err = ParentError::NotForwarded(ChildError::Error1);
145        let result = err.into_call_result();
146
147        match result {
148            CallResult::Failed {
149                module,
150                code,
151                message: _,
152            } => {
153                assert_eq!(module, "test2");
154                assert_eq!(code, 1);
155            }
156            _ => panic!("expected failed result, got: {:?}", result),
157        }
158
159        let err = ParentError::NotForwarded(ChildError::Error2);
160        let result = err.into_call_result();
161
162        match result {
163            CallResult::Failed {
164                module,
165                code,
166                message: _,
167            } => {
168                assert_eq!(module, "test2");
169                assert_eq!(code, 1);
170            }
171            _ => panic!("expected failed result, got: {:?}", result),
172        }
173    }
174
175    #[test]
176    fn test_error_sources_3() {
177        let err = ParentParentError::Nested(ParentError::Nested(ChildError::Error1));
178        let result = err.into_call_result();
179
180        match result {
181            CallResult::Failed {
182                module,
183                code,
184                message: _,
185            } => {
186                assert_eq!(module, "test1");
187                assert_eq!(code, 1);
188            }
189            _ => panic!("expected failed result, got: {:?}", result),
190        }
191    }
192}