use failure::Fail;
use http::StatusCode;
use std::fmt;
use endpoint::syntax::verb::Verbs;
use error::{Error, HttpError};
pub type ApplyResult<T> = Result<T, ApplyError>;
#[derive(Debug)]
pub struct ApplyError(ApplyErrorKind);
#[derive(Debug)]
enum ApplyErrorKind {
NotMatched,
MethodNotAllowed(Verbs),
Custom(Error),
}
impl ApplyError {
pub fn not_matched() -> ApplyError {
ApplyError(ApplyErrorKind::NotMatched)
}
pub fn method_not_allowed(allowed: Verbs) -> ApplyError {
ApplyError(ApplyErrorKind::MethodNotAllowed(allowed))
}
pub fn custom(err: impl Into<Error>) -> ApplyError {
ApplyError(ApplyErrorKind::Custom(err.into()))
}
#[doc(hidden)]
pub fn merge(self, other: ApplyError) -> ApplyError {
use self::ApplyErrorKind::*;
ApplyError(match (self.0, other.0) {
(Custom(reason), _) => Custom(reason),
(_, Custom(reason)) => Custom(reason),
(MethodNotAllowed(allowed1), MethodNotAllowed(allowed2)) => {
MethodNotAllowed(allowed1 | allowed2)
}
(NotMatched, MethodNotAllowed(allowed)) => MethodNotAllowed(allowed),
(MethodNotAllowed(allowed), NotMatched) => MethodNotAllowed(allowed),
(NotMatched, NotMatched) => NotMatched,
})
}
}
impl fmt::Display for ApplyError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::ApplyErrorKind::*;
match self.0 {
NotMatched => formatter.write_str("not matched"),
MethodNotAllowed(..) if !formatter.alternate() => {
formatter.write_str("method not allowed")
}
MethodNotAllowed(allowed) => {
write!(formatter, "method not allowed (allowed methods: ")?;
for (i, method) in allowed.into_iter().enumerate() {
if i > 0 {
formatter.write_str(", ")?;
}
method.fmt(formatter)?;
}
formatter.write_str(")")
}
Custom(ref err) => fmt::Display::fmt(err, formatter),
}
}
}
impl HttpError for ApplyError {
fn status_code(&self) -> StatusCode {
use self::ApplyErrorKind::*;
match self.0 {
NotMatched => StatusCode::NOT_FOUND,
MethodNotAllowed(..) => StatusCode::METHOD_NOT_ALLOWED,
Custom(ref err) => err.status_code(),
}
}
fn cause(&self) -> Option<&dyn Fail> {
match self.0 {
ApplyErrorKind::Custom(ref err) => err.cause(),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::Method;
#[test]
fn test_merge_1() {
let err1 = ApplyError::not_matched();
let err2 = ApplyError::not_matched();
let err = err1.merge(err2);
assert_matches!(err.0, ApplyErrorKind::NotMatched);
}
#[test]
fn test_merge_2() {
let err1 = ApplyError::not_matched();
let err2 = ApplyError::method_not_allowed(Verbs::GET);
assert_matches!(
err1.merge(err2).0,
ApplyErrorKind::MethodNotAllowed(allowed) if allowed.contains(&Method::GET)
);
}
#[test]
fn test_merge_3() {
let err1 = ApplyError::method_not_allowed(Verbs::GET);
let err2 = ApplyError::method_not_allowed(Verbs::POST);
assert_matches!(
err1.merge(err2).0,
ApplyErrorKind::MethodNotAllowed(allowed) if allowed.contains(&Method::GET) && allowed.contains(&Method::POST)
);
}
}