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
use http::StatusCode;
use std::{borrow::Cow, result::Result as StdResult};
use crate::HttpError;
/// Extension trait to map the error variant of a [`Result`] to a [`HttpError`].
pub trait ResultExt {
type Item;
/// Maps a `Result<T, E>` to `Result<T, HttpError<R>>` by creating a [`HttpError`] with the
/// specified status code wrapping the error contained [`Err`].
///
/// # Example
///
/// ```
/// use http::StatusCode;
/// use anyhow_http::{http_error, HttpError, ResultExt};
///
/// let s: Result<i32, HttpError> = "nan"
/// .parse::<i32>()
/// .map_status(StatusCode::BAD_REQUEST);
/// assert_eq!(s, Err(http_error!(BAD_REQUEST)));
/// ```
fn map_status(self, status_code: StatusCode) -> StdResult<Self::Item, HttpError>;
/// Maps a `Result<T, E>` to `Result<T, HttpError<R>>` by creating a [`HttpError`] with the
/// specified status code and reason wrapping the error contained [`Err`].
///
/// # Example
///
/// ```
/// use http::StatusCode;
/// use anyhow_http::{http_error, HttpError, ResultExt};
///
/// let s: Result<i32, HttpError> = "nan"
/// .parse::<i32>()
/// .map_http_error(StatusCode::BAD_REQUEST, "invalid number");
/// assert_eq!(s, Err(http_error!(BAD_REQUEST, "invalid number")));
/// ```
fn map_http_error<S>(
self,
status_code: StatusCode,
reason: S,
) -> StdResult<Self::Item, HttpError>
where
S: Into<Cow<'static, str>>;
}
impl<E, T> ResultExt for StdResult<T, E>
where
E: Into<anyhow::Error> + Send + Sync + 'static,
{
type Item = T;
fn map_status(self, status_code: StatusCode) -> StdResult<T, HttpError> {
match self {
Ok(v) => Ok(v),
Err(e) => Err(HttpError::from_err(e).with_status_code(status_code)),
}
}
fn map_http_error<S>(
self,
status_code: StatusCode,
reason: S,
) -> StdResult<Self::Item, HttpError>
where
S: Into<Cow<'static, str>>,
{
match self {
Ok(v) => Ok(v),
Err(e) => Err(HttpError::from_err(e)
.with_status_code(status_code)
.with_reason(reason.into())),
}
}
}
/// Extension trait to transform an [`Option`] to a [`HttpError`].
pub trait OptionExt {
type Item;
/// Transforms the `Option<T>` into a `Result<T, HttpError<R>>`, mapping `Some(v)` to
/// `Ok(v)` and `None` to `Err(HttpError)` with status code.
///
/// # Examples
///
/// ```
/// use http::StatusCode;
/// use anyhow_http::{http_error, HttpError, OptionExt};
///
/// let x: Result<_, HttpError> = None::<()>.ok_or_status(StatusCode::BAD_REQUEST);
/// assert_eq!(x, Err(http_error!(BAD_REQUEST)));
/// ```
fn ok_or_status(self, status_code: StatusCode) -> StdResult<Self::Item, HttpError>;
}
impl<T> OptionExt for std::option::Option<T> {
type Item = T;
fn ok_or_status(self, status_code: StatusCode) -> StdResult<T, HttpError> {
match self {
Some(v) => Ok(v),
None => Err(HttpError::from_status_code(status_code)),
}
}
}
#[cfg(test)]
mod tests {
use anyhow::anyhow;
use super::*;
#[test]
fn http_err_ext_result_map_status() {
let result: StdResult<(), _> = Err(anyhow!("error"));
let http_result: StdResult<_, HttpError> = result.map_status(StatusCode::BAD_REQUEST);
let Err(e) = http_result else { unreachable!() };
assert_eq!(e.status_code, StatusCode::BAD_REQUEST);
assert_eq!(e.source.unwrap().to_string(), "error".to_owned());
}
#[test]
fn http_err_ext_result_map_http_error() {
let s: StdResult<i32, HttpError> = "nan".parse::<i32>().map_status(StatusCode::BAD_REQUEST);
assert_eq!(s, Err(HttpError::from_status_code(StatusCode::BAD_REQUEST)));
let result: StdResult<(), _> = Err(anyhow!("error"));
let http_result: StdResult<_, HttpError> =
result.map_http_error(StatusCode::BAD_REQUEST, "invalid request");
let Err(e) = http_result else { unreachable!() };
assert_eq!(e.status_code, StatusCode::BAD_REQUEST);
assert_eq!(e.source.unwrap().to_string(), "error".to_owned());
assert_eq!(e.reason, Some("invalid request".into()));
}
#[test]
fn http_err_ext_option() {
let opt: Option<()> = None;
let http_result: StdResult<_, HttpError> = opt.ok_or_status(StatusCode::BAD_REQUEST);
let Err(e) = http_result else { unreachable!() };
assert_eq!(e.status_code, StatusCode::BAD_REQUEST);
assert!(e.source.is_none());
}
}