use super::{handle_error, IntoResponse};
use crate::{IntoResponseError, Response};
#[cfg(feature = "openapi")]
use crate::{NoContent, ResponseSchema};
use futures_util::future::{BoxFuture, FutureExt, TryFutureExt};
use gotham::hyper::{
header::{InvalidHeaderValue, LOCATION},
Body, StatusCode
};
#[cfg(feature = "openapi")]
use openapi_type::OpenapiSchema;
use std::{error::Error as StdError, fmt::Debug};
use thiserror::Error;
#[derive(Clone, Debug, Default)]
pub struct Redirect {
pub to: String
}
impl IntoResponse for Redirect {
type Err = InvalidHeaderValue;
fn into_response(self) -> BoxFuture<'static, Result<Response, Self::Err>> {
async move {
let mut res = Response::new(StatusCode::SEE_OTHER, Body::empty(), None);
res.header(LOCATION, self.to.parse()?);
Ok(res)
}
.boxed()
}
}
#[cfg(feature = "openapi")]
impl ResponseSchema for Redirect {
fn status_codes() -> Vec<StatusCode> {
vec![StatusCode::SEE_OTHER]
}
fn schema(code: StatusCode) -> OpenapiSchema {
assert_eq!(code, StatusCode::SEE_OTHER);
<NoContent as ResponseSchema>::schema(StatusCode::NO_CONTENT)
}
}
#[derive(Debug, Error)]
pub enum RedirectError<E: StdError + 'static> {
#[error("{0}")]
InvalidLocation(#[from] InvalidHeaderValue),
#[error("{0}")]
Other(#[source] E)
}
#[allow(ambiguous_associated_items)] impl<E> IntoResponse for Result<Redirect, E>
where
E: Debug + IntoResponseError,
<E as IntoResponseError>::Err: StdError + Sync
{
type Err = RedirectError<<E as IntoResponseError>::Err>;
fn into_response(self) -> BoxFuture<'static, Result<Response, Self::Err>> {
match self {
Ok(nc) => nc.into_response().map_err(Into::into).boxed(),
Err(e) => handle_error(e).map_err(RedirectError::Other).boxed()
}
}
}
#[cfg(feature = "openapi")]
impl<E> ResponseSchema for Result<Redirect, E>
where
E: Debug + IntoResponseError,
<E as IntoResponseError>::Err: StdError + Sync
{
fn status_codes() -> Vec<StatusCode> {
let mut status_codes = E::status_codes();
status_codes.push(StatusCode::SEE_OTHER);
status_codes
}
fn schema(code: StatusCode) -> OpenapiSchema {
match code {
StatusCode::SEE_OTHER => <Redirect as ResponseSchema>::schema(StatusCode::SEE_OTHER),
code => E::schema(code)
}
}
}
#[cfg(test)]
mod test {
use super::*;
use futures_executor::block_on;
use gotham::hyper::StatusCode;
use thiserror::Error;
#[derive(Debug, Default, Error)]
#[error("An Error")]
struct MsgError;
#[test]
fn redirect_response() {
let redir = Redirect {
to: "http://localhost/foo".to_owned()
};
let res = block_on(redir.into_response()).expect("didn't expect error response");
assert_eq!(res.status, StatusCode::SEE_OTHER);
assert_eq!(res.mime, None);
assert_eq!(
res.headers.get(LOCATION).map(|hdr| hdr.to_str().unwrap()),
Some("http://localhost/foo")
);
assert_eq!(res.full_body().unwrap(), &[] as &[u8]);
#[cfg(feature = "openapi")]
assert_eq!(Redirect::status_codes(), vec![StatusCode::SEE_OTHER]);
}
#[test]
fn redirect_result() {
let redir: Result<Redirect, MsgError> = Ok(Redirect {
to: "http://localhost/foo".to_owned()
});
let res = block_on(redir.into_response()).expect("didn't expect error response");
assert_eq!(res.status, StatusCode::SEE_OTHER);
assert_eq!(res.mime, None);
assert_eq!(
res.headers.get(LOCATION).map(|hdr| hdr.to_str().unwrap()),
Some("http://localhost/foo")
);
assert_eq!(res.full_body().unwrap(), &[] as &[u8]);
#[cfg(feature = "openapi")]
assert_eq!(<Result<Redirect, MsgError>>::status_codes(), vec![
StatusCode::INTERNAL_SERVER_ERROR,
StatusCode::SEE_OTHER
]);
}
}