#[cfg(feature = "actix")]
use actix_web::{HttpResponse, ResponseError, http::StatusCode as ActixStatus};
#[cfg(feature = "actix")]
use crate::response::actix_impl::respond_with_problem_json;
#[cfg(feature = "actix")]
use crate::{AppError, ProblemJson};
#[cfg(feature = "actix")]
impl ResponseError for AppError {
fn status_code(&self) -> ActixStatus {
ActixStatus::from_u16(self.kind.http_status())
.unwrap_or(ActixStatus::INTERNAL_SERVER_ERROR)
}
fn error_response(&self) -> HttpResponse {
self.emit_telemetry();
let problem = ProblemJson::from_ref(self);
respond_with_problem_json(problem)
}
}
#[cfg(all(test, feature = "actix"))]
mod actix_tests {
use std::str::FromStr;
use actix_web::{
ResponseError,
body::to_bytes,
http::header::{RETRY_AFTER, WWW_AUTHENTICATE}
};
use crate::{AppCode, AppError, AppErrorKind, AppResult};
#[test]
fn maps_status_consistently() {
let e = AppError::new(AppErrorKind::Validation, "bad");
assert_eq!(e.status_code().as_u16(), 422);
}
#[actix_web::test]
async fn error_response_sets_body_and_headers() -> AppResult<(), Box<dyn std::error::Error>> {
let err = AppError::unauthorized("no token")
.with_retry_after_secs(7)
.with_www_authenticate("Bearer");
let resp = err.error_response();
assert_eq!(resp.status().as_u16(), 401);
let headers = resp.headers().clone();
assert_eq!(
headers.get(RETRY_AFTER).and_then(|v| v.to_str().ok()),
Some("7")
);
assert_eq!(
headers.get(WWW_AUTHENTICATE).and_then(|v| v.to_str().ok()),
Some("Bearer")
);
let bytes = to_bytes(resp.into_body()).await?;
let body: serde_json::Value = serde_json::from_slice(&bytes)?;
assert_eq!(
body.get("status").and_then(|value| value.as_u64()),
Some(401)
);
assert_eq!(
body.get("code")
.and_then(|value| value.as_str())
.map(AppCode::from_str)
.transpose()
.expect("parse app code"),
Some(AppCode::Unauthorized)
);
assert_eq!(
body.get("detail").and_then(|value| value.as_str()),
Some("no token")
);
Ok(())
}
}