#[doc(hidden)]
pub mod execution_error;
mod common_errors;
mod core_errors;
mod error_kind;
mod error_match;
mod execution_status;
#[cfg(test)]
mod tests;
pub use self::{
common_errors::CommonError, core_errors::CoreError, error_kind::ErrorKind,
error_match::ErrorMatch, execution_status::ExecutionStatus,
};
use exonum_derive::*;
use exonum_merkledb::Error as MerkledbError;
use exonum_proto::ProtobufConvert;
use thiserror::Error;
use std::{
fmt::{self, Display},
panic,
};
use super::{CallInfo, InstanceId, MethodId};
use crate::proto::schema::errors as errors_proto;
pub trait ExecutionFail {
fn kind(&self) -> ErrorKind;
fn description(&self) -> String;
fn with_description(&self, description: impl Display) -> ExecutionError {
ExecutionError::new(self.kind(), description.to_string())
}
}
#[derive(Clone, Debug, Error, BinaryValue)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ExecutionError {
kind: ErrorKind,
description: String,
runtime_id: Option<u32>,
call_site: Option<CallSite>,
}
#[derive(Debug, Clone, ProtobufConvert, BinaryValue)]
#[protobuf_convert(source = "errors_proto::ExecutionErrorAux")]
pub(crate) struct ExecutionErrorAux {
pub description: String,
}
pub fn catch_panic<F, T>(maybe_panic: F) -> Result<T, ExecutionError>
where
F: FnOnce() -> Result<T, ExecutionError>,
{
let result = panic::catch_unwind(panic::AssertUnwindSafe(maybe_panic));
match result {
Ok(Err(e)) => Err(e),
Err(panic) => {
if panic.is::<MerkledbError>() {
panic::resume_unwind(panic);
}
Err(ExecutionError::from_panic(panic))
}
Ok(Ok(value)) => Ok(value),
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, BinaryValue)]
#[non_exhaustive]
pub struct CallSite {
pub instance_id: InstanceId,
#[serde(flatten)]
pub call_type: CallType,
}
impl CallSite {
pub(crate) fn new(instance_id: InstanceId, call_type: CallType) -> Self {
Self {
instance_id,
call_type,
}
}
pub(crate) fn from_call_info(call_info: &CallInfo, interface: impl Into<String>) -> Self {
Self {
instance_id: call_info.instance_id,
call_type: CallType::Method {
interface: interface.into(),
id: call_info.method_id,
},
}
}
}
impl fmt::Display for CallSite {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"{} of service {}",
self.call_type, self.instance_id
)
}
}
impl ProtobufConvert for CallSite {
type ProtoStruct = errors_proto::CallSite;
fn to_pb(&self) -> Self::ProtoStruct {
use errors_proto::CallSite_Type::*;
let mut pb = Self::ProtoStruct::new();
pb.set_instance_id(self.instance_id);
match &self.call_type {
CallType::Constructor => pb.set_call_type(CONSTRUCTOR),
CallType::Resume => pb.set_call_type(RESUME),
CallType::Method { interface, id } => {
pb.set_call_type(METHOD);
pb.set_interface(interface.clone());
pb.set_method_id(*id);
}
CallType::BeforeTransactions => pb.set_call_type(BEFORE_TRANSACTIONS),
CallType::AfterTransactions => pb.set_call_type(AFTER_TRANSACTIONS),
}
pb
}
fn from_pb(mut pb: Self::ProtoStruct) -> anyhow::Result<Self> {
use errors_proto::CallSite_Type::*;
let call_type = match pb.get_call_type() {
CONSTRUCTOR => CallType::Constructor,
RESUME => CallType::Resume,
BEFORE_TRANSACTIONS => CallType::BeforeTransactions,
AFTER_TRANSACTIONS => CallType::AfterTransactions,
METHOD => CallType::Method {
interface: pb.take_interface(),
id: pb.get_method_id(),
},
};
Ok(Self::new(pb.get_instance_id(), call_type))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "call_type", rename_all = "snake_case")]
#[non_exhaustive]
pub enum CallType {
Constructor,
Resume,
Method {
#[serde(default, skip_serializing_if = "String::is_empty")]
interface: String,
#[serde(rename = "method_id")]
id: MethodId,
},
BeforeTransactions,
AfterTransactions,
}
impl fmt::Display for CallType {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Constructor => formatter.write_str("constructor"),
Self::Resume => formatter.write_str("resuming routine"),
Self::Method { interface, id } if interface.is_empty() => {
write!(formatter, "method {}", id)
}
Self::Method { interface, id } => write!(formatter, "{}::(method {})", interface, id),
Self::BeforeTransactions => formatter.write_str("before_transactions hook"),
Self::AfterTransactions => formatter.write_str("after_transactions hook"),
}
}
}