1
0
Fork 0
mirror of https://gitlab.com/msrd0/gotham-restful.git synced 2025-02-22 20:52:27 +00:00

Add a Redirect type that can be returned by endpoints

This commit is contained in:
msrd0 2021-01-26 17:49:11 +00:00
parent cf0223473f
commit 441a42c75e
4 changed files with 148 additions and 1 deletions

View file

@ -38,6 +38,7 @@ openapiv3 = { version = "0.3.2", optional = true }
regex = { version = "1.4", optional = true }
serde = { version = "1.0.110", features = ["derive"] }
serde_json = "1.0.58"
thiserror = "1.0"
uuid = { version = "0.8.1", optional = true }
[dev-dependencies]

View file

@ -481,7 +481,7 @@ pub use response::Response;
mod result;
pub use result::{
AuthError, AuthError::Forbidden, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponseError, NoContent, Raw,
AuthError, AuthError::Forbidden, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponseError, NoContent, Raw, Redirect,
ResourceResult, Success
};

View file

@ -20,6 +20,9 @@ pub use no_content::NoContent;
mod raw;
pub use raw::Raw;
mod redirect;
pub use redirect::Redirect;
#[allow(clippy::module_inception)]
mod result;
pub use result::IntoResponseError;

143
src/result/redirect.rs Normal file
View file

@ -0,0 +1,143 @@
use super::{handle_error, NoContent, ResourceResult};
#[cfg(feature = "openapi")]
use crate::OpenapiSchema;
use crate::{IntoResponseError, Response};
use futures_util::future::{BoxFuture, FutureExt, TryFutureExt};
use gotham::hyper::{
header::{InvalidHeaderValue, LOCATION},
Body, StatusCode
};
use std::{
error::Error as StdError,
fmt::{Debug, Display}
};
use thiserror::Error;
/**
This is the return type of a resource that only returns a redirect. It will result
in a _303 See Other_ answer, meaning the redirect will always result in a GET request
on the target.
```
# #[macro_use] extern crate gotham_restful_derive;
# mod doc_tests_are_broken {
# use gotham::state::State;
# use gotham_restful::*;
#
# #[derive(Resource)]
# #[resource(read_all)]
# struct MyResource;
#
#[read_all]
fn read_all() -> Redirect {
Redirect {
to: "http://localhost:8080/cool/new/location".to_owned()
}
}
# }
```
*/
#[derive(Clone, Debug, Default)]
pub struct Redirect {
pub to: String
}
impl ResourceResult 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")]
fn default_status() -> StatusCode {
StatusCode::SEE_OTHER
}
#[cfg(feature = "openapi")]
fn schema() -> OpenapiSchema {
<NoContent as ResourceResult>::schema()
}
}
// private type due to parent mod
#[derive(Debug, Error)]
pub enum RedirectError<E: StdError + 'static> {
#[error("{0}")]
InvalidLocation(#[from] InvalidHeaderValue),
#[error("{0}")]
Other(#[source] E)
}
#[allow(ambiguous_associated_items)] // an enum variant is not a type. never.
impl<E> ResourceResult for Result<Redirect, E>
where
E: Display + IntoResponseError,
<E as IntoResponseError>::Err: 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(|e| RedirectError::Other(e)).boxed()
}
}
#[cfg(feature = "openapi")]
fn default_status() -> StatusCode {
Redirect::default_status()
}
#[cfg(feature = "openapi")]
fn schema() -> OpenapiSchema {
<Redirect as ResourceResult>::schema()
}
}
#[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 rediect_has_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]);
}
#[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]);
}
}