From 873aec578773a9e19219937d74c63cd158bc1ba6 Mon Sep 17 00:00:00 2001 From: mahmoud-eltahawy <95278610+mahmoud-eltahawy@users.noreply.github.com> Date: Fri, 19 Jul 2024 01:20:00 +0300 Subject: [PATCH] feat: allow using enums for `StaticSegment` by implementing `AsPath` (#2685) --- .../src/matching/horizontal/static_segment.rs | 114 ++++++++++++++++-- 1 file changed, 105 insertions(+), 9 deletions(-) diff --git a/router/src/matching/horizontal/static_segment.rs b/router/src/matching/horizontal/static_segment.rs index f7c00f94c..695f10741 100644 --- a/router/src/matching/horizontal/static_segment.rs +++ b/router/src/matching/horizontal/static_segment.rs @@ -1,6 +1,6 @@ use super::{PartialPathMatch, PathSegment, PossibleRouteMatch}; use core::iter; -use std::borrow::Cow; +use std::{borrow::Cow, fmt::Debug}; impl PossibleRouteMatch for () { type ParamsIter = iter::Empty<(Cow<'static, str>, String)>; @@ -15,10 +15,20 @@ impl PossibleRouteMatch for () { fn generate_path(&self, _path: &mut Vec) {} } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct StaticSegment(pub &'static str); +pub trait AsPath { + fn as_path(&self) -> &'static str; +} -impl PossibleRouteMatch for StaticSegment { +impl AsPath for &'static str { + fn as_path(&self) -> &'static str { + self + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct StaticSegment(pub T); + +impl PossibleRouteMatch for StaticSegment { type ParamsIter = iter::Empty<(Cow<'static, str>, String)>; fn test<'a>( @@ -27,17 +37,19 @@ impl PossibleRouteMatch for StaticSegment { ) -> Option> { let mut matched_len = 0; let mut test = path.chars().peekable(); - let mut this = self.0.chars(); - let mut has_matched = self.0.is_empty() || self.0 == "/"; + let mut this = self.0.as_path().chars(); + let mut has_matched = + self.0.as_path().is_empty() || self.0.as_path() == "/"; // match an initial / if let Some('/') = test.peek() { test.next(); - if !self.0.is_empty() { + if !self.0.as_path().is_empty() { matched_len += 1; } - if self.0.starts_with('/') || self.0.is_empty() { + if self.0.as_path().starts_with('/') || self.0.as_path().is_empty() + { this.next(); } } @@ -70,14 +82,33 @@ impl PossibleRouteMatch for StaticSegment { } fn generate_path(&self, path: &mut Vec) { - path.push(PathSegment::Static(self.0.into())) + path.push(PathSegment::Static(self.0.as_path().into())) } } #[cfg(test)] mod tests { + use crate::AsPath; + use super::{PossibleRouteMatch, StaticSegment}; + #[derive(Debug, Clone)] + enum Paths { + Foo, + Bar, + } + + impl AsPath for Paths { + fn as_path(&self) -> &'static str { + match self { + Foo => "foo", + Bar => "bar", + } + } + } + + use Paths::*; + #[test] fn single_static_match() { let path = "/foo"; @@ -89,6 +120,17 @@ mod tests { assert!(params.is_empty()); } + #[test] + fn single_static_match_on_enum() { + let path = "/foo"; + let def = StaticSegment(Foo); + let matched = def.test(path).expect("couldn't match route"); + assert_eq!(matched.matched(), "/foo"); + assert_eq!(matched.remaining(), ""); + let params = matched.params().collect::>(); + assert!(params.is_empty()); + } + #[test] fn single_static_mismatch() { let path = "/foo"; @@ -96,6 +138,13 @@ mod tests { assert!(def.test(path).is_none()); } + #[test] + fn single_static_mismatch_on_enum() { + let path = "/foo"; + let def = StaticSegment(Bar); + assert!(def.test(path).is_none()); + } + #[test] fn single_static_match_with_trailing_slash() { let path = "/foo/"; @@ -107,6 +156,17 @@ mod tests { assert!(params.is_empty()); } + #[test] + fn single_static_match_with_trailing_slash_on_enum() { + let path = "/foo/"; + let def = StaticSegment(Foo); + let matched = def.test(path).expect("couldn't match route"); + assert_eq!(matched.matched(), "/foo"); + assert_eq!(matched.remaining(), "/"); + let params = matched.params().collect::>(); + assert!(params.is_empty()); + } + #[test] fn tuple_of_static_matches() { let path = "/foo/bar"; @@ -118,6 +178,17 @@ mod tests { assert!(params.is_empty()); } + #[test] + fn tuple_of_static_matches_on_enum() { + let path = "/foo/bar"; + let def = (StaticSegment(Foo), 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().collect::>(); + assert!(params.is_empty()); + } + #[test] fn tuple_static_mismatch() { let path = "/foo/baz"; @@ -125,6 +196,13 @@ mod tests { assert!(def.test(path).is_none()); } + #[test] + fn tuple_static_mismatch_on_enum() { + let path = "/foo/baz"; + let def = (StaticSegment(Foo), StaticSegment(Bar)); + assert!(def.test(path).is_none()); + } + #[test] fn arbitrary_nesting_of_tuples_has_no_effect_on_matching() { let path = "/foo/bar"; @@ -142,4 +220,22 @@ mod tests { let params = matched.params().collect::>(); assert!(params.is_empty()); } + + #[test] + fn arbitrary_nesting_of_tuples_has_no_effect_on_matching_on_enum() { + let path = "/foo/bar"; + let def = ( + (), + (StaticSegment(Foo)), + (), + ((), ()), + 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().collect::>(); + assert!(params.is_empty()); + } }