use anyhow::ensure;
use exonum_derive::*;
use exonum_proto::ProtobufConvert;
use super::ExecutionError;
use crate::proto::schema::errors as errors_proto;
#[derive(Clone, Debug, Serialize, Deserialize, BinaryValue)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ExecutionStatus(#[serde(with = "self::serde")] pub Result<(), ExecutionError>);
impl ExecutionStatus {
pub fn ok() -> Self {
Self(Ok(()))
}
pub fn err(err: impl Into<ExecutionError>) -> Self {
Self(Err(err.into()))
}
}
impl From<Result<(), ExecutionError>> for ExecutionStatus {
fn from(inner: Result<(), ExecutionError>) -> Self {
Self(inner)
}
}
impl ProtobufConvert for ExecutionStatus {
type ProtoStruct = errors_proto::ExecutionStatus;
fn to_pb(&self) -> Self::ProtoStruct {
let mut inner = Self::ProtoStruct::default();
match &self.0 {
Result::Ok(_) => inner.set_ok(protobuf::well_known_types::Empty::new()),
Result::Err(e) => inner.set_error(e.to_pb()),
}
inner
}
fn from_pb(mut pb: Self::ProtoStruct) -> anyhow::Result<Self> {
let inner = if pb.has_error() {
ensure!(!pb.has_ok(), "ExecutionStatus has both of variants.");
Err(ExecutionError::from_pb(pb.take_error())?)
} else {
Ok(())
};
Ok(Self(inner))
}
}
pub mod serde {
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
use crate::runtime::{CallSite, ErrorKind, ExecutionError};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum ExecutionType {
Success,
UnexpectedError,
CommonError,
CoreError,
RuntimeError,
ServiceError,
}
#[doc(hidden)]
#[derive(Debug, Serialize, Deserialize)]
pub struct ExecutionStatus {
#[serde(rename = "type")]
typ: ExecutionType,
#[serde(skip_serializing_if = "String::is_empty", default)]
description: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
code: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none", default)]
runtime_id: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none", default)]
call_site: Option<CallSite>,
}
impl From<Result<(), &ExecutionError>> for ExecutionStatus {
fn from(inner: Result<(), &ExecutionError>) -> Self {
if let Err(err) = inner {
let (typ, code) = match err.kind {
ErrorKind::Unexpected => (ExecutionType::UnexpectedError, None),
ErrorKind::Common { code } => (ExecutionType::CommonError, Some(code)),
ErrorKind::Core { code } => (ExecutionType::CoreError, Some(code)),
ErrorKind::Runtime { code } => (ExecutionType::RuntimeError, Some(code)),
ErrorKind::Service { code } => (ExecutionType::ServiceError, Some(code)),
};
Self {
typ,
description: err.description.clone(),
code,
runtime_id: err.runtime_id,
call_site: err.call_site.clone(),
}
} else {
Self {
typ: ExecutionType::Success,
description: String::new(),
code: None,
runtime_id: None,
call_site: None,
}
}
}
}
impl ExecutionStatus {
pub(crate) fn into_result(self) -> Result<Result<(), ExecutionError>, &'static str> {
Ok(if let ExecutionType::Success = self.typ {
Ok(())
} else {
let kind = match self.typ {
ExecutionType::UnexpectedError => {
if self.code != None {
return Err("Code specified for an unexpected error");
}
ErrorKind::Unexpected
}
ExecutionType::CommonError => ErrorKind::Common {
code: self.code.ok_or("No code specified")?,
},
ExecutionType::CoreError => ErrorKind::Core {
code: self.code.ok_or("No code specified")?,
},
ExecutionType::RuntimeError => ErrorKind::Runtime {
code: self.code.ok_or("No code specified")?,
},
ExecutionType::ServiceError => ErrorKind::Service {
code: self.code.ok_or("No code specified")?,
},
ExecutionType::Success => unreachable!(),
};
Err(ExecutionError {
kind,
description: self.description,
runtime_id: self.runtime_id,
call_site: self.call_site,
})
})
}
}
pub fn serialize<S>(
inner: &Result<(), ExecutionError>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
ExecutionStatus::from(inner.as_ref().map(|_| ())).serialize(serializer)
}
pub fn deserialize<'a, D>(deserializer: D) -> Result<Result<(), ExecutionError>, D::Error>
where
D: Deserializer<'a>,
{
ExecutionStatus::deserialize(deserializer)
.and_then(|status| status.into_result().map_err(D::Error::custom))
}
}