use crate::config::RewriteRule;
use regex::Regex;
#[derive(Debug)]
pub enum RewriteAction {
Internal(String),
Redirect {
target: String,
status: u16,
},
}
pub struct RewriteEngine {
rules: Vec<(Regex, String, String)>,
}
impl RewriteEngine {
pub fn new(rules: &[RewriteRule]) -> Self {
let compiled: Vec<_> = rules
.iter()
.filter_map(|rule| {
Regex::new(&rule.pattern)
.ok()
.map(|re| (re, rule.replacement.clone(), rule.flag.clone()))
})
.collect();
Self { rules: compiled }
}
pub fn is_empty(&self) -> bool {
self.rules.is_empty()
}
pub fn apply(&self, path: &str) -> Option<RewriteAction> {
for (re, replacement, flag) in &self.rules {
if re.is_match(path) {
let result = re.replace(path, replacement.as_str()).into_owned();
return Some(match flag.as_str() {
"redirect" => RewriteAction::Redirect { target: result, status: 302 },
"permanent" => RewriteAction::Redirect { target: result, status: 301 },
_ => RewriteAction::Internal(result),
});
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rule(pattern: &str, replacement: &str, flag: &str) -> RewriteRule {
RewriteRule {
pattern: pattern.into(),
replacement: replacement.into(),
flag: flag.into(),
}
}
#[test]
fn internal_rewrite() {
let e = RewriteEngine::new(&[rule("^/old/(.*)$", "/new/$1", "break")]);
match e.apply("/old/page") {
Some(RewriteAction::Internal(p)) => assert_eq!(p, "/new/page"),
other => panic!("unexpected: {:?}", other.is_some()),
}
}
#[test]
fn redirect_flags_map_to_statuses() {
let e = RewriteEngine::new(&[rule("^/moved$", "/here", "permanent")]);
match e.apply("/moved") {
Some(RewriteAction::Redirect { target, status }) => {
assert_eq!(target, "/here");
assert_eq!(status, 301);
}
_ => panic!("expected redirect"),
}
}
#[test]
fn no_match_passes_through() {
let e = RewriteEngine::new(&[rule("^/old$", "/new", "break")]);
assert!(e.apply("/other").is_none());
}
}