use axum::{
extract::FromRequestParts,
http::{
request::Parts,
uri::{self},
StatusCode, Uri,
},
response::Redirect,
};
#[derive(Debug)]
pub struct RequestContext {
script_name: Option<String>,
}
impl RequestContext {
pub fn internal<S: AsRef<str>>(&self, path: S) -> String {
let mut parts: uri::Parts = Default::default();
if let Some(ref script_name) = self.script_name {
parts.path_and_query = Some(
(script_name.clone() + "/" + path.as_ref())
.parse()
.expect("should not fail to parse"),
);
} else {
parts.path_and_query = Some(
path.as_ref()
.parse()
.expect("tried to generate invalid Uri"),
);
}
Uri::from_parts(parts)
.expect("should not fail to construct relative uri")
.to_string()
}
#[inline(always)]
pub fn redirect_to(&self, path: &str) -> Redirect {
Redirect::to(&self.internal(path))
}
}
impl<S: Send + Sync> FromRequestParts<S> for RequestContext {
type Rejection = StatusCode;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let script_name = if let Some(script_name_header) = parts.headers.get("X-Script-Name") {
Some(
script_name_header
.to_str()
.map_err(|_| StatusCode::BAD_GATEWAY)?
.to_owned(),
)
} else {
None
};
Ok(RequestContext { script_name })
}
}
#[cfg(test)]
mod tests {
use super::RequestContext;
#[test]
fn internal_url_construction_without_reverse_proxy() {
let ctx = RequestContext { script_name: None };
assert_eq!(ctx.internal("/foo/bar"), "/foo/bar");
}
#[test]
fn internal_url_construction_with_reverse_proxy() {
let ctx = RequestContext {
script_name: Some("/sub/dir".to_owned()),
};
assert_eq!(ctx.internal("foo/bar"), "/sub/dir/foo/bar");
}
}