use super::{PartialPathMatch, PathSegment, PossibleRouteMatch};
use core::iter;
use std::borrow::Cow;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ParamSegment(pub &'static str);
impl PossibleRouteMatch for ParamSegment {
fn optional(&self) -> bool {
false
}
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
let mut matched_len = 0;
let mut param_offset = 0;
let mut param_len = 0;
let mut test = path.chars();
if let Some('/') = test.next() {
matched_len += 1;
param_offset = 1;
}
for char in test {
if char == '/' {
break;
}
else {
matched_len += char.len_utf8();
param_len += char.len_utf8();
}
}
if matched_len == 0 || (matched_len == 1 && path.starts_with('/')) {
return None;
}
let (matched, remaining) = path.split_at(matched_len);
let param_value = vec![(
Cow::Borrowed(self.0),
path[param_offset..param_len + param_offset].to_string(),
)];
Some(PartialPathMatch::new(remaining, param_value, matched))
}
fn generate_path(&self, path: &mut Vec<PathSegment>) {
path.push(PathSegment::Param(self.0.into()));
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct WildcardSegment(pub &'static str);
impl PossibleRouteMatch for WildcardSegment {
fn optional(&self) -> bool {
false
}
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
let mut matched_len = 0;
let mut param_offset = 0;
let mut param_len = 0;
let mut test = path.chars();
if let Some('/') = test.next() {
matched_len += 1;
param_offset += 1;
}
for char in test {
matched_len += char.len_utf8();
param_len += char.len_utf8();
}
let (matched, remaining) = path.split_at(matched_len);
let param_value = iter::once((
Cow::Borrowed(self.0),
path[param_offset..param_len + param_offset].to_string(),
));
Some(PartialPathMatch::new(
remaining,
param_value.into_iter().collect(),
matched,
))
}
fn generate_path(&self, path: &mut Vec<PathSegment>) {
path.push(PathSegment::Splat(self.0.into()));
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct OptionalParamSegment(pub &'static str);
impl PossibleRouteMatch for OptionalParamSegment {
fn optional(&self) -> bool {
true
}
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
let mut matched_len = 0;
let mut param_offset = 0;
let mut param_len = 0;
let mut test = path.chars();
if let Some('/') = test.next() {
matched_len += 1;
param_offset = 1;
}
for char in test {
if char == '/' {
break;
}
else {
matched_len += char.len_utf8();
param_len += char.len_utf8();
}
}
let matched_len = if matched_len == 1 && path.starts_with('/') {
0
} else {
matched_len
};
let (matched, remaining) = path.split_at(matched_len);
let param_value = (matched_len > 0)
.then(|| {
(
Cow::Borrowed(self.0),
path[param_offset..param_len + param_offset].to_string(),
)
})
.into_iter()
.collect();
Some(PartialPathMatch::new(remaining, param_value, matched))
}
fn generate_path(&self, path: &mut Vec<PathSegment>) {
path.push(PathSegment::OptionalParam(self.0.into()));
}
}
#[cfg(test)]
mod tests {
use super::PossibleRouteMatch;
use crate::{
OptionalParamSegment, ParamSegment, StaticSegment, WildcardSegment,
};
#[test]
fn single_param_match() {
let path = "/foo";
let def = ParamSegment("a");
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo");
assert_eq!(matched.remaining(), "");
let params = matched.params();
assert_eq!(params[0], ("a".into(), "foo".into()));
}
#[test]
fn single_param_match_with_trailing_slash() {
let path = "/foo/";
let def = ParamSegment("a");
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo");
assert_eq!(matched.remaining(), "/");
let params = matched.params();
assert_eq!(params[0], ("a".into(), "foo".into()));
}
#[test]
fn tuple_of_param_matches() {
let path = "/foo/bar";
let def = (ParamSegment("a"), ParamSegment("b"));
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo/bar");
assert_eq!(matched.remaining(), "");
let params = matched.params();
assert_eq!(params[0], ("a".into(), "foo".into()));
assert_eq!(params[1], ("b".into(), "bar".into()));
}
#[test]
fn splat_should_match_all() {
let path = "/foo/bar/////";
let def = (
StaticSegment("foo"),
StaticSegment("bar"),
WildcardSegment("rest"),
);
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo/bar/////");
assert_eq!(matched.remaining(), "");
let params = matched.params();
assert_eq!(params[0], ("rest".into(), "////".into()));
}
#[test]
fn optional_param_can_match() {
let path = "/foo";
let def = OptionalParamSegment("a");
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo");
assert_eq!(matched.remaining(), "");
let params = matched.params();
assert_eq!(params[0], ("a".into(), "foo".into()));
}
#[test]
fn optional_param_can_not_match() {
let path = "/";
let def = OptionalParamSegment("a");
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "");
assert_eq!(matched.remaining(), "/");
let params = matched.params();
assert_eq!(params.first(), None);
}
#[test]
fn optional_params_match_first() {
let path = "/foo";
let def = (OptionalParamSegment("a"), OptionalParamSegment("b"));
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo");
assert_eq!(matched.remaining(), "");
let params = matched.params();
assert_eq!(params[0], ("a".into(), "foo".into()));
}
#[test]
fn optional_params_can_match_both() {
let path = "/foo/bar";
let def = (OptionalParamSegment("a"), OptionalParamSegment("b"));
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo/bar");
assert_eq!(matched.remaining(), "");
let params = matched.params();
assert_eq!(params[0], ("a".into(), "foo".into()));
assert_eq!(params[1], ("b".into(), "bar".into()));
}
#[test]
fn matching_after_optional_param() {
let path = "/bar";
let def = (OptionalParamSegment("a"), StaticSegment("bar"));
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/bar");
assert_eq!(matched.remaining(), "");
let params = matched.params();
assert!(params.is_empty());
}
#[test]
fn static_before_param() {
let path = "/foo/bar";
let def = (StaticSegment("foo"), ParamSegment("b"));
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo/bar");
assert_eq!(matched.remaining(), "");
let params = matched.params();
assert_eq!(params[0], ("b".into(), "bar".into()));
}
#[test]
fn static_before_optional_param() {
let path = "/foo/bar";
let def = (StaticSegment("foo"), OptionalParamSegment("b"));
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo/bar");
assert_eq!(matched.remaining(), "");
let params = matched.params();
assert_eq!(params[0], ("b".into(), "bar".into()));
}
#[test]
fn multiple_optional_params_match_first() {
let path = "/foo/bar";
let def = (
OptionalParamSegment("a"),
OptionalParamSegment("b"),
StaticSegment("bar"),
);
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo/bar");
assert_eq!(matched.remaining(), "");
let params = matched.params();
assert_eq!(params[0], ("a".into(), "foo".into()));
}
#[test]
fn multiple_optionals_can_match_both() {
let path = "/foo/qux/bar";
let def = (
OptionalParamSegment("a"),
OptionalParamSegment("b"),
StaticSegment("bar"),
);
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo/qux/bar");
assert_eq!(matched.remaining(), "");
let params = matched.params();
assert_eq!(params[0], ("a".into(), "foo".into()));
assert_eq!(params[1], ("b".into(), "qux".into()));
}
}