use core::error::Error;
use core::fmt::{self, Display};
use core::str::FromStr;
use http::status::InvalidStatusCode;
pub use http::StatusCode;
use serde::{Deserialize, Serialize};
use crate::uri::{RelativeRef, Uri, UriParseError};
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(untagged)]
pub enum RedirectTo {
Uri(Uri),
Path(RelativeRef),
}
impl RedirectTo {
pub fn is_uri(&self) -> bool {
matches!(self, Self::Uri(..))
}
pub fn is_path(&self) -> bool {
matches!(self, Self::Path(..))
}
pub fn path(&self) -> &str {
match self {
Self::Uri(uri) => uri.path(),
Self::Path(fake) => fake.path(),
}
}
pub fn resolve(&self, base: Uri) -> Result<Uri, UriParseError> {
match self {
Self::Uri(uri) => Ok(uri.clone()),
Self::Path(fake) => base.join(fake.path()),
}
}
}
impl From<Uri> for RedirectTo {
fn from(uri: Uri) -> Self {
Self::Uri(uri)
}
}
impl From<RelativeRef> for RedirectTo {
fn from(relative_ref: RelativeRef) -> Self {
Self::Path(relative_ref)
}
}
impl FromStr for RedirectTo {
type Err = UriParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with('/') {
RelativeRef::parse(s).map(Self::from)
} else {
Uri::parse(s).map(Self::from)
}
}
}
impl Display for RedirectTo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Uri(uri) => uri.fmt(f),
Self::Path(path) => path.fmt(f),
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Redirect {
from: RelativeRef,
to: RedirectTo,
#[serde(with = "crate::http_serde::status_code")]
code: StatusCode,
}
impl Redirect {
pub fn from(&self) -> &RelativeRef {
&self.from
}
pub fn to(&self) -> &RedirectTo {
&self.to
}
pub fn code(&self) -> StatusCode {
self.code
}
}
#[derive(Debug)]
pub enum RedirectParseError {
MissingFrom,
InvalidFrom(UriParseError),
MissingTo,
InvalidTo(UriParseError),
MissingCode,
InvalidCode(InvalidStatusCode),
}
impl Display for RedirectParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingFrom => write!(f, "missing redirect from"),
Self::InvalidFrom(err) => write!(f, "invalid redirect from: {}", err),
Self::MissingTo => write!(f, "missing redirect to"),
Self::InvalidTo(err) => write!(f, "invalid redirect to: {}", err),
Self::MissingCode => write!(f, "missing status code"),
Self::InvalidCode(err) => write!(f, "invalid status code: {}", err),
}
}
}
impl Error for RedirectParseError {}
impl FromStr for Redirect {
type Err = RedirectParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut iter = s.split_whitespace();
let from = iter.next().ok_or(RedirectParseError::MissingFrom)?;
let from = RelativeRef::parse(from).map_err(RedirectParseError::InvalidFrom)?;
let to = iter
.next()
.ok_or(RedirectParseError::MissingTo)?
.parse::<RedirectTo>()
.map_err(RedirectParseError::InvalidTo)?;
let code = iter
.next()
.ok_or(RedirectParseError::MissingCode)?
.parse::<StatusCode>()
.map_err(RedirectParseError::InvalidCode)?;
Ok(Self { from, to, code })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_redirects() {
const REDIRECTS: &'static str = r#"
/abc /def 200
/:name /def/:name 400
/abcd https://www.example.com/ 302
"#;
let redirects = REDIRECTS
.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.map(|l| l.parse::<Redirect>())
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(
redirects
.iter()
.map(|r| (r.from().to_string(), r.to().to_string(), r.code().as_u16()))
.collect::<Vec<_>>(),
[
("/abc", "/def", 200u16),
("/:name", "/def/:name", 400u16),
("/abcd", "https://www.example.com/", 302u16),
]
.map(|(from, to, code)| (from.to_string(), to.to_string(), code)),
);
}
}