use crate::routers::Route;
use nom::{
IResult, Parser,
branch::alt,
bytes::complete::{tag, take_while1},
character::complete::{anychar, char},
combinator::map,
multi::many0,
};
use reinhardt_http::Handler;
pub fn path<H>(pattern: impl Into<String>, handler: H) -> Route
where
H: Handler + 'static,
{
Route::from_handler(pattern, handler)
}
pub fn re_path<H>(regex: impl Into<String>, handler: H) -> Route
where
H: Handler + 'static,
{
let regex_str = regex.into();
let pattern = convert_regex_to_pattern(®ex_str);
Route::from_handler(pattern, handler)
}
fn convert_regex_to_pattern(regex: &str) -> String {
let mut result = regex.to_string();
result = result.strip_prefix("^").unwrap_or(&result).to_string();
result = result.strip_suffix("$").unwrap_or(&result).to_string();
match parse_regex_pattern(&result) {
Ok((_, converted)) => converted,
Err(_) => result, }
}
fn parse_regex_pattern(input: &str) -> IResult<&str, String> {
let (input, parts) = many0(alt((
map(parse_named_group, |name| format!("{{{}}}", name)),
map(parse_escaped_char, String::from),
map(parse_non_group_char, |c| c.to_string()),
)))
.parse(input)?;
Ok((input, parts.join("")))
}
fn parse_named_group(input: &str) -> IResult<&str, String> {
let (input, _) = tag("(?P<")(input)?;
let (input, name) = take_while1(|c: char| c.is_alphanumeric() || c == '_')(input)?;
let (input, _) = char('>')(input)?;
let (input, _) = parse_group_content(input)?;
Ok((input, name.to_string()))
}
fn parse_group_content(input: &str) -> IResult<&str, String> {
let mut depth = 1;
let mut chars = Vec::new();
let mut remaining = input;
let mut escaped = false;
while !remaining.is_empty() && depth > 0 {
let (rest, c) = anychar(remaining)?;
if escaped {
chars.push(c);
escaped = false;
} else if c == '\\' {
chars.push(c);
escaped = true;
} else if c == '(' {
chars.push(c);
depth += 1;
} else if c == ')' {
depth -= 1;
if depth > 0 {
chars.push(c);
}
} else {
chars.push(c);
}
remaining = rest;
}
Ok((remaining, chars.into_iter().collect()))
}
fn parse_escaped_char(input: &str) -> IResult<&str, String> {
let (input, _) = char('\\')(input)?;
let (input, c) = anychar(input)?;
let result = match c {
'/' | '.' | '-' | '_' => c.to_string(),
'd' | 'w' | 's' | 'D' | 'W' | 'S' | 'b' | 'B' => format!("\\{}", c),
_ => format!("\\{}", c),
};
Ok((input, result))
}
fn parse_non_group_char(input: &str) -> IResult<&str, char> {
let (input, c) = anychar(input)?;
if c == '(' {
if input.starts_with("?P<") {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
}
if c == '\\' {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
Ok((input, c))
}
pub struct IncludedRouter {
pub prefix: String,
pub routes: Vec<Route>,
pub namespace: Option<String>,
}
impl IncludedRouter {
pub fn new(prefix: impl Into<String>, routes: Vec<Route>) -> Self {
Self {
prefix: prefix.into(),
routes,
namespace: None,
}
}
pub fn with_namespace(mut self, namespace: impl Into<String>) -> Self {
self.namespace = Some(namespace.into());
self
}
}
pub fn include_routes(prefix: impl Into<String>, routes: Vec<Route>) -> IncludedRouter {
IncludedRouter::new(prefix, routes)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::routers_macros::path;
#[test]
fn test_convert_regex_to_pattern() {
assert_eq!(convert_regex_to_pattern(r"^users/$"), "users/");
assert_eq!(
convert_regex_to_pattern(r"^users/(?P<id>\d+)/$"),
"users/{id}/"
);
assert_eq!(
convert_regex_to_pattern(r"^users/(?P<user_id>\d+)/posts/(?P<post_id>\d+)/$"),
"users/{user_id}/posts/{post_id}/"
);
assert_eq!(
convert_regex_to_pattern(r"^articles/(?P<year>\d{4})/(?P<month>\d{2})/$"),
"articles/{year}/{month}/"
);
assert_eq!(
convert_regex_to_pattern(r"^data/(?P<slug>[a-z]+(-[a-z]+)*)/$"),
"data/{slug}/"
);
assert_eq!(
convert_regex_to_pattern(r"^files/(?P<path>[\w\/\-\.]+)/$"),
"files/{path}/"
);
assert_eq!(
convert_regex_to_pattern(r"^api\/v1/users/(?P<id>\d+)/$"),
"api/v1/users/{id}/"
);
assert_eq!(
convert_regex_to_pattern(r"^(?P<category>\w+)/(?P<slug>[\w-]+)/(?P<id>\d+)/$"),
"{category}/{slug}/{id}/"
);
assert_eq!(
convert_regex_to_pattern(r"users/(?P<id>\d+)/"),
"users/{id}/"
);
assert_eq!(
convert_regex_to_pattern(r"^users/(?P<id>\d+)/"),
"users/{id}/"
);
assert_eq!(
convert_regex_to_pattern(r"users/(?P<id>\d+)/$"),
"users/{id}/"
);
}
#[test]
fn test_included_router_namespace() {
let routes = vec![];
let included = IncludedRouter::new(path!("/api"), routes).with_namespace("api");
assert_eq!(included.prefix, path!("/api"));
assert_eq!(included.namespace, Some("api".to_string()));
}
}