use axum_core::response::{IntoResponse, Response};
use http::{header::LOCATION, HeaderValue, StatusCode};
#[must_use = "needs to be returned from a handler or otherwise turned into a Response to be useful"]
#[derive(Debug, Clone)]
pub struct Redirect {
status_code: StatusCode,
location: String,
}
impl Redirect {
pub fn to(uri: &str) -> Self {
Self::with_status_code(StatusCode::SEE_OTHER, uri)
}
pub fn temporary(uri: &str) -> Self {
Self::with_status_code(StatusCode::TEMPORARY_REDIRECT, uri)
}
pub fn permanent(uri: &str) -> Self {
Self::with_status_code(StatusCode::PERMANENT_REDIRECT, uri)
}
#[must_use]
pub fn status_code(&self) -> StatusCode {
self.status_code
}
#[must_use]
pub fn location(&self) -> &str {
&self.location
}
fn with_status_code(status_code: StatusCode, uri: &str) -> Self {
assert!(
status_code.is_redirection(),
"not a redirection status code"
);
Self {
status_code,
location: uri.to_owned(),
}
}
}
impl IntoResponse for Redirect {
fn into_response(self) -> Response {
match HeaderValue::try_from(self.location) {
Ok(location) => (self.status_code, [(LOCATION, location)]).into_response(),
Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(),
}
}
}
#[cfg(test)]
mod tests {
use super::Redirect;
use axum_core::response::IntoResponse;
use http::StatusCode;
const EXAMPLE_URL: &str = "https://example.com";
#[test]
fn correct_status() {
assert_eq!(
StatusCode::SEE_OTHER,
Redirect::to(EXAMPLE_URL).status_code()
);
assert_eq!(
StatusCode::TEMPORARY_REDIRECT,
Redirect::temporary(EXAMPLE_URL).status_code()
);
assert_eq!(
StatusCode::PERMANENT_REDIRECT,
Redirect::permanent(EXAMPLE_URL).status_code()
);
}
#[test]
fn correct_location() {
assert_eq!(EXAMPLE_URL, Redirect::permanent(EXAMPLE_URL).location());
assert_eq!("/redirect", Redirect::permanent("/redirect").location())
}
#[test]
fn test_internal_error() {
let response = Redirect::permanent("Axum is awesome, \n but newlines aren't allowed :(")
.into_response();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}