1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
//! The Lambda runtime errors crate defines the `LambdaErrorExt` trait
//! that can be used by libriaries to return errors compatible with the
//! AWS Lambda Rust runtime.
//!
//! This crate also exports the `lambda_runtime_errors_derive` crate to
//! derive the `LambdaErrorExt` trait.
//!
//! ```rust,no-run
//! use lambda_runtime_errors::*;
//!
//! // the generated error_type() method returns "crate::LambdaError"
//! #[derive(LambdaErrorExt)]
//! struct LambdaError;
//! ```
mod error_ext_impl;

pub use crate::error_ext_impl::*;
pub use lambda_runtime_errors_derive::*;

use failure::{format_err, Compat, Error, Fail};
use std::fmt;

/// The `LambdaErrorExt` trait defines the `error_type()` method used
/// by the AWS Lambda runtime client to generate `ErrorResponse`
/// objects. The value returned by the `error_type()` method is used to
/// populate the `errorType` field in the Lambda response.  This crate
/// includes an implementation of this trait for most errors in the
/// standard library. By default, error return their type name.
pub trait LambdaErrorExt {
    /// The value for this field should be an alphanumeric unique identifier
    /// of the error type. For example `MyCustomError`.
    ///
    /// # Return
    /// An alphanumeric identifier for the error
    fn error_type(&self) -> &str;
}

impl LambdaErrorExt for Error {
    fn error_type(&self) -> &str {
        self.find_root_cause().name().unwrap_or_else(|| "FailureError")
    }
}

// We implement this trait here so that we can use the Compat type
// in the lambda-runtime crate - heaps of fun between failure and std::error
impl LambdaErrorExt for Compat<Error> {
    fn error_type(&self) -> &str {
        "CompatFailureError"
    }
}

/// `Result` type extension for AWS that makes it easy to generate a `HandlerError`
/// object or a `Compat<Error>` from the failure crate using an existing result.
/// This trait should be imported from the `lambda_runtime_core` or `lambda_runtime`
/// crates.
pub trait LambdaResultExt<OK, ERR> {
    /// Takes the incoming `Result` and maps it to a Result that returns an `HandlerError` object.
    /// The `HandlerError` type already includes implementations of the `From` trait for most
    /// standard library errors. This method is intended to be used when a the `From` trait is not
    /// implemented.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use lambda_runtime_core::{Context, LambdaResultExt, HandlerError, lambda};
    /// use std::error::Error as StdError;
    ///
    /// fn main() -> Result<(), Box<dyn StdError>> {
    ///     lambda!(my_handler);
    ///     Ok(())
    /// }
    ///
    /// fn my_handler(_event: Vec<u8>, _ctx: Context) -> Result<Vec<u8>, HandlerError> {
    ///     let age = "hello"; // this will throw an error when we try to parse it into an int
    ///     age.parse::<u8>().handler_error()?;
    ///
    ///     Ok(vec!())
    /// }
    /// ```
    fn handler_error(self) -> Result<OK, HandlerError>;

    /// Takes the incoming result and converts it into an `Error` type from the `failure` crate
    /// wrapped in a `Compat` object to make it implement the `Error` trait from the standard
    /// library. This method makes it easy to write handler functions that return `Compat<Error>`
    /// directly.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use lambda_runtime_core::{Context, LambdaResultExt, lambda};
    /// use failure::{Error, Compat};
    /// use std::error::Error as StdError;
    ///
    /// fn main() -> Result<(), Box<dyn StdError>> {
    ///     lambda!(my_handler);
    ///     Ok(())
    /// }
    ///
    /// fn my_handler(_event: Vec<u8>, _ctx: Context) -> Result<Vec<u8>, Compat<Error>> {
    ///     let age = "hello"; // this will throw an error when we try to parse it into an int
    ///     age.parse::<u8>().failure_compat()?;
    ///     Ok(vec!())
    /// }
    /// ```
    fn failure_compat(self) -> Result<OK, Compat<Error>>;
}

impl<OK, ERR> LambdaResultExt<OK, ERR> for Result<OK, ERR>
where
    ERR: Fail + LambdaErrorExt,
{
    fn handler_error(self) -> Result<OK, HandlerError> {
        self.map_err(HandlerError::new)
    }

    fn failure_compat(self) -> Result<OK, Compat<Error>> {
        self.map_err(|err| Error::from(err).compat())
    }
}

/// The `HandlerError` struct can be use to abstract any `Err` of the handler method `Result`.
/// The `HandlerError` object can be generated `From` any object that supports `Display`,
/// `Send, `Sync`, and `Debug`. This allows handler functions to return any error using
/// the `?` syntax. For example `let _age_num: u8 = e.age.parse()?;` will return the
/// `<F as FromStr>::Err` from the handler function.
//pub type HandlerError = failure::Error;
#[derive(Debug)]
pub struct HandlerError {
    err_type: String,
    inner: failure::Error,
}
impl HandlerError {
    pub fn new<T: failure::Fail + LambdaErrorExt + Send + Sync>(e: T) -> Self {
        let err_type = e.error_type().to_owned();
        HandlerError {
            err_type,
            inner: failure::Error::from(e),
        }
    }
}
impl std::error::Error for HandlerError {}
impl fmt::Display for HandlerError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}: {}", self.err_type, self.inner.find_root_cause())
    }
}
impl LambdaErrorExt for HandlerError {
    fn error_type(&self) -> &str {
        &self.err_type
    }
}
impl From<&str> for HandlerError {
    fn from(s: &str) -> Self {
        HandlerError {
            err_type: "UnknownError".to_owned(),
            inner: format_err!("{}", s),
        }
    }
}
impl From<failure::Error> for HandlerError {
    fn from(e: failure::Error) -> Self {
        let error_type = e.error_type();
        HandlerError {
            err_type: error_type.to_owned(),
            inner: e,
        }
    }
}
impl From<serde_json::error::Error> for HandlerError {
    fn from(e: serde_json::error::Error) -> Self {
        HandlerError {
            err_type: "JsonError".to_owned(),
            inner: failure::Error::from(e),
        }
    }
}

#[cfg(test)]
pub(crate) mod tests {
    use super::*;
    use failure::Fail;

    #[derive(Fail, Debug)]
    #[fail(display = "Custom Error")]
    struct CustomError;

    #[test]
    fn std_error_type() {
        let parsed_int = "hello".parse::<u8>();
        let err = HandlerError::from(parsed_int.err().unwrap());
        assert_eq!(err.error_type(), "std::num::ParseIntError");
    }

    #[test]
    fn error_type_from_failure() {
        let err = HandlerError::from(failure::Error::from(CustomError {}));
        assert_eq!(err.error_type(), "lambda_runtime_errors::tests::CustomError");
    }
}