use axum::{
extract::FromRequestParts,
http::{
request::Parts,
uri::{self},
StatusCode, Uri,
},
response::Redirect,
};
#[derive(Debug)]
pub struct Mount {
script_name: Option<String>,
}
impl Mount {
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 {
let path = format!(
"{}/{}",
script_name.trim_end_matches('/'),
path.as_ref().trim_start_matches('/'),
);
parts.path_and_query = Some(path.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 Mount {
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(Mount { script_name })
}
}
#[cfg(test)]
mod tests {
use super::Mount;
#[test]
fn internal_url_construction_without_reverse_proxy() {
let mount = Mount { script_name: None };
assert_eq!(mount.internal("/foo/bar"), "/foo/bar");
}
#[test]
fn internal_url_construction_with_reverse_proxy() {
let mount = Mount {
script_name: Some("/sub/dir///".to_owned()),
};
assert_eq!(mount.internal("foo/bar"), "/sub/dir/foo/bar");
assert_eq!(mount.internal("///foo/bar"), "/sub/dir/foo/bar");
}
}