use failure::{AsFail, Backtrace, Context, Fail};
use lambda_runtime_errors::LambdaErrorExt;
use log::*;
use serde_derive::*;
use std::{
fmt::{self, Display},
option::Option,
};
pub const RUNTIME_ERROR_TYPE: &str = "RustRuntimeError";
#[derive(Serialize)]
pub struct ErrorResponse {
#[serde(rename = "errorMessage")]
pub error_message: String,
#[serde(rename = "errorType")]
pub error_type: String,
#[serde(rename = "stackTrace")]
pub stack_trace: Option<Vec<String>>,
}
impl ErrorResponse {
fn new(message: String, err_type: String, backtrace: Option<&Backtrace>) -> Self {
let mut err = ErrorResponse {
error_message: message,
error_type: err_type,
stack_trace: Option::default(),
};
if let Some(stack) = backtrace {
trace!("Begin backtrace collection");
err.stack_trace = Some(
format!("{:?}", stack)
.lines()
.map(std::string::ToString::to_string)
.collect::<Vec<String>>(),
);
trace!("Completed backtrace collection");
}
err
}
}
impl<T: AsFail + LambdaErrorExt + Display> From<T> for ErrorResponse {
fn from(e: T) -> Self {
ErrorResponse::new(format!("{}", e), e.error_type().to_owned(), e.as_fail().backtrace())
}
}
#[derive(Debug)]
pub struct ApiError {
inner: Context<ApiErrorKind>,
}
impl ApiError {
pub fn is_recoverable(&self) -> bool {
match *self.inner.get_context() {
ApiErrorKind::Recoverable(_) => true,
_ => false,
}
}
}
#[derive(Clone, PartialEq, Debug, Fail)]
pub enum ApiErrorKind {
#[fail(display = "Recoverable API error: {}", _0)]
Recoverable(String),
#[fail(display = "Unrecoverable API error: {}", _0)]
Unrecoverable(String),
}
impl Fail for ApiError {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
}
impl Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl LambdaErrorExt for ApiError {
fn error_type(&self) -> &str {
"RuntimeApiError"
}
}
impl From<ApiErrorKind> for ApiError {
fn from(kind: ApiErrorKind) -> Self {
Self {
inner: Context::new(kind),
}
}
}
impl From<Context<ApiErrorKind>> for ApiError {
fn from(inner: Context<ApiErrorKind>) -> Self {
Self { inner }
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use failure::format_err;
use std::env;
#[test]
fn does_not_produce_stack_trace() {
env::remove_var("RUST_BACKTRACE");
let err = format_err!("Test error").compat();
let resp_err = ErrorResponse::from(err);
assert_eq!(resp_err.stack_trace, None);
}
#[test]
fn is_recoverable_eq_correctly() {
let rec_err = ApiError::from(ApiErrorKind::Recoverable("Some recoverable kind".to_owned()));
assert_eq!(true, rec_err.is_recoverable());
let unrec_err = ApiError::from(ApiErrorKind::Unrecoverable("Some unrecovrable kind".to_owned()));
assert_eq!(false, unrec_err.is_recoverable());
}
}