use fvm_ipld_encoding::de::DeserializeOwned;
use fvm_ipld_encoding::ipld_block::IpldBlock;
use std::fmt::Display;
use fvm_shared4::error::ExitCode;
use thiserror::Error;
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[error("ActorError(exit_code: {exit_code:?}, msg: {msg})")]
pub struct ActorError {
exit_code: ExitCode,
data: Option<IpldBlock>,
msg: String,
}
impl ActorError {
pub fn unchecked(code: ExitCode, msg: String) -> Self {
Self {
exit_code: code,
msg,
data: None,
}
}
pub fn unchecked_with_data(code: ExitCode, msg: String, data: Option<IpldBlock>) -> Self {
Self {
exit_code: code,
msg,
data,
}
}
pub fn checked(code: ExitCode, msg: String, data: Option<IpldBlock>) -> Self {
let exit_code = match code {
ExitCode::SYS_MISSING_RETURN
| ExitCode::SYS_ILLEGAL_INSTRUCTION
| ExitCode::SYS_ILLEGAL_EXIT_CODE => ExitCode::USR_UNSPECIFIED,
code if code.is_system_error() => ExitCode::USR_ASSERTION_FAILED,
code => code,
};
Self {
exit_code,
msg,
data,
}
}
pub fn illegal_argument(msg: String) -> Self {
Self {
exit_code: ExitCode::USR_ILLEGAL_ARGUMENT,
msg,
data: None,
}
}
pub fn not_found(msg: String) -> Self {
Self {
exit_code: ExitCode::USR_NOT_FOUND,
msg,
data: None,
}
}
pub fn forbidden(msg: String) -> Self {
Self {
exit_code: ExitCode::USR_FORBIDDEN,
msg,
data: None,
}
}
pub fn insufficient_funds(msg: String) -> Self {
Self {
exit_code: ExitCode::USR_INSUFFICIENT_FUNDS,
msg,
data: None,
}
}
pub fn illegal_state(msg: String) -> Self {
Self {
exit_code: ExitCode::USR_ILLEGAL_STATE,
msg,
data: None,
}
}
pub fn serialization(msg: String) -> Self {
Self {
exit_code: ExitCode::USR_SERIALIZATION,
msg,
data: None,
}
}
pub fn unhandled_message(msg: String) -> Self {
Self {
exit_code: ExitCode::USR_UNHANDLED_MESSAGE,
msg,
data: None,
}
}
pub fn unspecified(msg: String) -> Self {
Self {
exit_code: ExitCode::USR_UNSPECIFIED,
msg,
data: None,
}
}
pub fn assertion_failed(msg: String) -> Self {
Self {
exit_code: ExitCode::USR_ASSERTION_FAILED,
msg,
data: None,
}
}
pub fn read_only(msg: String) -> Self {
Self {
exit_code: ExitCode::USR_READ_ONLY,
msg,
data: None,
}
}
pub fn exit_code(&self) -> ExitCode {
self.exit_code
}
pub fn msg(&self) -> &str {
&self.msg
}
pub fn take_data(&mut self) -> Option<IpldBlock> {
std::mem::take(&mut self.data)
}
pub fn wrap(mut self, msg: impl AsRef<str>) -> Self {
self.msg = format!("{}: {}", msg.as_ref(), self.msg);
self
}
}
impl From<fvm_ipld_encoding::Error> for ActorError {
fn from(e: fvm_ipld_encoding::Error) -> Self {
Self {
exit_code: ExitCode::USR_SERIALIZATION,
msg: e.to_string(),
data: None,
}
}
}
#[macro_export]
macro_rules! actor_error_v13 {
( $code:ident; $msg:expr ) => { $crate::v13::ActorError::$code($msg.to_string()) };
( $code:ident; $msg:literal $(, $ex:expr)+ ) => {
$crate::v13::ActorError::$code(format!($msg, $($ex,)*))
};
( $code:ident, $msg:expr ) => { $crate::actor_error_v13!($code; $msg) };
( $code:ident, $msg:literal $(, $ex:expr)+ ) => {
$crate::actor_error_v13!($code; $msg $(, $ex)*)
};
}
pub trait ActorContext<T> {
fn context<C>(self, context: C) -> Result<T, ActorError>
where
C: Display + 'static;
fn with_context<C, F>(self, f: F) -> Result<T, ActorError>
where
C: Display + 'static,
F: FnOnce() -> C;
}
impl<T, E> ActorContext<T> for Result<T, E>
where
E: Into<ActorError>,
{
fn context<C>(self, context: C) -> Result<T, ActorError>
where
C: Display + 'static,
{
self.map_err(|err| {
let mut err = err.into();
err.msg = format!("{}: {}", context, err.msg);
err
})
}
fn with_context<C, F>(self, f: F) -> Result<T, ActorError>
where
C: Display + 'static,
F: FnOnce() -> C,
{
self.map_err(|err| {
let mut err = err.into();
err.msg = format!("{}: {}", f(), err.msg);
err
})
}
}
pub trait AsActorError<T>: Sized {
fn exit_code(self, code: ExitCode) -> Result<T, ActorError>;
fn context_code<C>(self, code: ExitCode, context: C) -> Result<T, ActorError>
where
C: Display + 'static;
fn with_context_code<C, F>(self, code: ExitCode, f: F) -> Result<T, ActorError>
where
C: Display + 'static,
F: FnOnce() -> C;
}
impl<T, E: Display> AsActorError<T> for Result<T, E> {
fn exit_code(self, code: ExitCode) -> Result<T, ActorError> {
self.map_err(|err| ActorError {
exit_code: code,
msg: err.to_string(),
data: None,
})
}
fn context_code<C>(self, code: ExitCode, context: C) -> Result<T, ActorError>
where
C: Display + 'static,
{
self.map_err(|err| ActorError {
exit_code: code,
msg: format!("{}: {}", context, err),
data: None,
})
}
fn with_context_code<C, F>(self, code: ExitCode, f: F) -> Result<T, ActorError>
where
C: Display + 'static,
F: FnOnce() -> C,
{
self.map_err(|err| ActorError {
exit_code: code,
msg: format!("{}: {}", f(), err),
data: None,
})
}
}
impl<T> AsActorError<T> for Option<T> {
fn exit_code(self, code: ExitCode) -> Result<T, ActorError> {
self.ok_or_else(|| ActorError {
exit_code: code,
msg: "None".to_string(),
data: None,
})
}
fn context_code<C>(self, code: ExitCode, context: C) -> Result<T, ActorError>
where
C: Display + 'static,
{
self.ok_or_else(|| ActorError {
exit_code: code,
msg: context.to_string(),
data: None,
})
}
fn with_context_code<C, F>(self, code: ExitCode, f: F) -> Result<T, ActorError>
where
C: Display + 'static,
F: FnOnce() -> C,
{
self.ok_or_else(|| ActorError {
exit_code: code,
msg: f().to_string(),
data: None,
})
}
}
pub fn deserialize_block<T>(ret: Option<IpldBlock>) -> Result<T, ActorError>
where
T: DeserializeOwned,
{
ret.context_code(
ExitCode::USR_ASSERTION_FAILED,
"return expected".to_string(),
)?
.deserialize()
.exit_code(ExitCode::USR_SERIALIZATION)
}