fil_actors_shared/v10/
actor_error.rs

1// Copyright 2019-2022 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use fvm_ipld_encoding::de::DeserializeOwned;
5use fvm_ipld_encoding::ipld_block::IpldBlock;
6use std::fmt::Display;
7
8use fvm_shared3::error::ExitCode;
9use thiserror::Error;
10
11/// The error type returned by actor method calls.
12#[derive(Error, Debug, Clone, PartialEq, Eq)]
13#[error("ActorError(exit_code: {exit_code:?}, msg: {msg})")]
14pub struct ActorError {
15    /// The exit code for this invocation.
16    /// Codes less than `FIRST_USER_EXIT_CODE` are prohibited and will be overwritten by the VM.
17    exit_code: ExitCode,
18    /// Message for debugging purposes,
19    msg: String,
20}
21
22/// Convenience macro for generating Actor Errors
23#[macro_export]
24macro_rules! actor_error_v10 {
25    // Error with only one stringable expression
26    ( $code:ident; $msg:expr ) => { $crate::v10::ActorError::$code($msg.to_string()) };
27
28    // String with positional arguments
29    ( $code:ident; $msg:literal $(, $ex:expr)+ ) => {
30        $crate::v10::ActorError::$code(format!($msg, $($ex,)*))
31    };
32
33    // Error with only one stringable expression, with comma separator
34    ( $code:ident, $msg:expr ) => { $crate::actor_error_v10!($code; $msg) };
35
36    // String with positional arguments, with comma separator
37    ( $code:ident, $msg:literal $(, $ex:expr)+ ) => {
38        $crate::actor_error_v10!($code; $msg $(, $ex)*)
39    };
40}
41
42impl ActorError {
43    /// Creates a new `ActorError`. This method does not check that the code is in the
44    /// range of valid actor abort codes.
45    pub fn unchecked(code: ExitCode, msg: String) -> Self {
46        Self {
47            exit_code: code,
48            msg,
49        }
50    }
51
52    pub fn illegal_argument(msg: String) -> Self {
53        Self {
54            exit_code: ExitCode::USR_ILLEGAL_ARGUMENT,
55            msg,
56        }
57    }
58    pub fn not_found(msg: String) -> Self {
59        Self {
60            exit_code: ExitCode::USR_NOT_FOUND,
61            msg,
62        }
63    }
64    pub fn forbidden(msg: String) -> Self {
65        Self {
66            exit_code: ExitCode::USR_FORBIDDEN,
67            msg,
68        }
69    }
70    pub fn insufficient_funds(msg: String) -> Self {
71        Self {
72            exit_code: ExitCode::USR_INSUFFICIENT_FUNDS,
73            msg,
74        }
75    }
76    pub fn illegal_state(msg: String) -> Self {
77        Self {
78            exit_code: ExitCode::USR_ILLEGAL_STATE,
79            msg,
80        }
81    }
82    pub fn serialization(msg: String) -> Self {
83        Self {
84            exit_code: ExitCode::USR_SERIALIZATION,
85            msg,
86        }
87    }
88    pub fn unhandled_message(msg: String) -> Self {
89        Self {
90            exit_code: ExitCode::USR_UNHANDLED_MESSAGE,
91            msg,
92        }
93    }
94    pub fn unspecified(msg: String) -> Self {
95        Self {
96            exit_code: ExitCode::USR_UNSPECIFIED,
97            msg,
98        }
99    }
100    pub fn assertion_failed(msg: String) -> Self {
101        Self {
102            exit_code: ExitCode::USR_ASSERTION_FAILED,
103            msg,
104        }
105    }
106
107    /// Returns the exit code of the error.
108    pub fn exit_code(&self) -> ExitCode {
109        self.exit_code
110    }
111
112    /// Error message of the actor error.
113    pub fn msg(&self) -> &str {
114        &self.msg
115    }
116
117    /// Prefix error message with a string message.
118    pub fn wrap(mut self, msg: impl AsRef<str>) -> Self {
119        self.msg = format!("{}: {}", msg.as_ref(), self.msg);
120        self
121    }
122}
123
124/// Converts a raw encoding error into an `ErrSerialization`.
125impl From<fvm_ipld_encoding::Error> for ActorError {
126    fn from(e: fvm_ipld_encoding::Error) -> Self {
127        Self {
128            exit_code: ExitCode::USR_SERIALIZATION,
129            msg: e.to_string(),
130        }
131    }
132}
133
134// Adds context to an actor error's descriptive message.
135pub trait ActorContext<T> {
136    fn context<C>(self, context: C) -> Result<T, ActorError>
137    where
138        C: Display + 'static;
139
140    fn with_context<C, F>(self, f: F) -> Result<T, ActorError>
141    where
142        C: Display + 'static,
143        F: FnOnce() -> C;
144}
145
146impl<T> ActorContext<T> for Result<T, ActorError> {
147    fn context<C>(self, context: C) -> Result<T, ActorError>
148    where
149        C: Display + 'static,
150    {
151        self.map_err(|mut err| {
152            err.msg = format!("{}: {}", context, err.msg);
153            err
154        })
155    }
156
157    fn with_context<C, F>(self, f: F) -> Result<T, ActorError>
158    where
159        C: Display + 'static,
160        F: FnOnce() -> C,
161    {
162        self.map_err(|mut err| {
163            err.msg = format!("{}: {}", f(), err.msg);
164            err
165        })
166    }
167}
168
169// Adapts a target into an actor error.
170pub trait AsActorError<T>: Sized {
171    fn exit_code(self, code: ExitCode) -> Result<T, ActorError>;
172
173    fn context_code<C>(self, code: ExitCode, context: C) -> Result<T, ActorError>
174    where
175        C: Display + 'static;
176
177    fn with_context_code<C, F>(self, code: ExitCode, f: F) -> Result<T, ActorError>
178    where
179        C: Display + 'static,
180        F: FnOnce() -> C;
181}
182
183// Note: E should be std::error::Error, revert to this after anyhow:Error is no longer used.
184impl<T, E: Display> AsActorError<T> for Result<T, E> {
185    fn exit_code(self, code: ExitCode) -> Result<T, ActorError> {
186        self.map_err(|err| ActorError {
187            exit_code: code,
188            msg: err.to_string(),
189        })
190    }
191
192    fn context_code<C>(self, code: ExitCode, context: C) -> Result<T, ActorError>
193    where
194        C: Display + 'static,
195    {
196        self.map_err(|err| ActorError {
197            exit_code: code,
198            msg: format!("{}: {}", context, err),
199        })
200    }
201
202    fn with_context_code<C, F>(self, code: ExitCode, f: F) -> Result<T, ActorError>
203    where
204        C: Display + 'static,
205        F: FnOnce() -> C,
206    {
207        self.map_err(|err| ActorError {
208            exit_code: code,
209            msg: format!("{}: {}", f(), err),
210        })
211    }
212}
213
214impl<T> AsActorError<T> for Option<T> {
215    fn exit_code(self, code: ExitCode) -> Result<T, ActorError> {
216        self.ok_or_else(|| ActorError {
217            exit_code: code,
218            msg: "None".to_string(),
219        })
220    }
221
222    fn context_code<C>(self, code: ExitCode, context: C) -> Result<T, ActorError>
223    where
224        C: Display + 'static,
225    {
226        self.ok_or_else(|| ActorError {
227            exit_code: code,
228            msg: context.to_string(),
229        })
230    }
231
232    fn with_context_code<C, F>(self, code: ExitCode, f: F) -> Result<T, ActorError>
233    where
234        C: Display + 'static,
235        F: FnOnce() -> C,
236    {
237        self.ok_or_else(|| ActorError {
238            exit_code: code,
239            msg: f().to_string(),
240        })
241    }
242}
243
244pub fn deserialize_block<T>(ret: Option<IpldBlock>) -> Result<T, ActorError>
245where
246    T: DeserializeOwned,
247{
248    ret.context_code(
249        ExitCode::USR_ASSERTION_FAILED,
250        "return expected".to_string(),
251    )?
252    .deserialize()
253    .exit_code(ExitCode::USR_SERIALIZATION)
254}