use crate::{RawResponse, Response, Result, protocol::HttpResponse};
pub fn rejection<Body>(status: u16, body: impl AsRef<[u8]>) -> Result<Response<Body>> {
build_rejection(
"rejection",
HttpResponse::status(status)
.body(body.as_ref().to_vec())
.build(),
)
}
pub fn rejection_from<Body>(response: HttpResponse) -> Result<Response<Body>> {
build_rejection("rejection_from", response)
}
fn build_rejection<Body>(caller: &str, response: HttpResponse) -> Result<Response<Body>> {
let status = response.status;
let raw = RawResponse::try_from(response).unwrap_or_else(|_| {
panic!("{caller} called with an out-of-range status code ({status}, must be 100–999)")
});
match Response::<Vec<u8>>::new(raw) {
Err(error) => Err(error),
Ok(_) => panic!(
"{caller} called with status {status}, which is not a client (4xx) or server (5xx) \
error — a feature receives that as Ok(Response), which ResponseBuilder builds"
),
}
}
#[cfg(test)]
mod tests {
use crate::{HttpError, protocol::HttpResponse};
use super::{rejection, rejection_from};
#[test]
fn carries_code_reason_and_body() {
let error = rejection::<Vec<u8>>(409, r#"{"error":"management cycle"}"#)
.expect_err("4xx is always an error");
let HttpError::Http {
code,
message,
headers,
body,
} = error
else {
panic!("expected HttpError::Http, got {error:?}")
};
assert_eq!(code, 409);
assert_eq!(message, "409 Conflict");
assert!(headers.is_empty(), "rejection() sets no headers");
assert_eq!(body, br#"{"error":"management cycle"}"#);
}
#[test]
fn rejection_is_the_header_less_case_of_rejection_from() {
let sugar = rejection::<Vec<u8>>(409, "nope");
let explicit =
rejection_from::<Vec<u8>>(HttpResponse::status(409).body(b"nope".to_vec()).build());
assert_eq!(sugar, explicit);
}
#[test]
fn rejection_from_keeps_the_headers() {
let error = rejection_from::<Vec<u8>>(
HttpResponse::status(401)
.header("www-authenticate", r#"Bearer error="invalid_token""#)
.header("content-type", "application/problem+json")
.build(),
)
.expect_err("a 401 is never Ok");
assert_eq!(
error.header("www-authenticate").unwrap(),
r#"Bearer error="invalid_token""#
);
assert_eq!(
error.content_type().map(|mime| mime.to_string()),
Some("application/problem+json".to_string())
);
}
#[test]
fn matches_the_real_conversion() {
let from_helper =
rejection::<String>(422, "name is required").expect_err("4xx is always an error");
let raw = crate::RawResponse::try_from(
HttpResponse::status(422)
.body(b"name is required".to_vec())
.build(),
)
.expect("422 is a valid status");
let from_shell = crate::Response::<Vec<u8>>::new(raw).expect_err("4xx is always an error");
assert_eq!(from_helper, from_shell);
}
#[test]
fn body_is_optional() {
let error = rejection::<Vec<u8>>(404, "").expect_err("4xx is always an error");
assert_eq!(error.code(), Some(404));
assert_eq!(error.body(), None);
}
#[test]
#[should_panic(expected = "rejection called with status 200, which is not a client")]
fn refuses_a_success_status() {
let _ = rejection::<Vec<u8>>(200, "");
}
#[test]
#[should_panic(expected = "rejection_from called with status 200, which is not a client")]
fn rejection_from_refuses_a_success_status() {
let _ = rejection_from::<Vec<u8>>(HttpResponse::status(200).build());
}
#[test]
#[should_panic(expected = "rejection called with an out-of-range status code (99")]
fn refuses_an_out_of_range_status() {
let _ = rejection::<Vec<u8>>(99, "");
}
}