hyperlane/route/
impl.rs

1use crate::*;
2
3// Associate a plugin registry with the specified type.
4collect!(HookMacro);
5
6/// Provides a default implementation for RouteMatcher.
7impl Default for RouteMatcher {
8    /// Creates a new, empty RouteMatcher.
9    ///
10    /// # Returns
11    ///
12    /// - `RouteMatcher` - A new RouteMatcher with empty storage for static, dynamic, and regex route.
13    #[inline(always)]
14    fn default() -> Self {
15        Self {
16            static_route: hash_map_xx_hash3_64(),
17            dynamic_route: hash_map_xx_hash3_64(),
18            regex_route: hash_map_xx_hash3_64(),
19        }
20    }
21}
22
23/// Implements the `PartialEq` trait for `RoutePattern`.
24///
25/// This allows for comparing two `RoutePattern` instances for equality.
26impl PartialEq for RoutePattern {
27    /// Checks if two `RoutePattern` instances are equal.
28    ///
29    /// # Arguments
30    ///
31    /// - `&Self` - The other `RoutePattern` instance to compare against.
32    ///
33    /// # Returns
34    ///
35    /// - `bool`- `true` if the instances are equal, `false` otherwise.
36    #[inline(always)]
37    fn eq(&self, other: &Self) -> bool {
38        self.get_0() == other.get_0()
39    }
40}
41
42/// Implements the `Eq` trait for `RoutePattern`.
43///
44/// This indicates that `RoutePattern` has a total equality relation.
45impl Eq for RoutePattern {}
46
47/// Implements the `Hash` trait for `RoutePattern`.
48///
49/// This allows `RoutePattern` to be used as a key in hash-based collections.
50impl Hash for RoutePattern {
51    /// Hashes the `RoutePattern` instance.
52    ///
53    /// # Arguments
54    ///
55    /// - `&mut Hasher` - The hasher to use.
56    #[inline(always)]
57    fn hash<H: Hasher>(&self, state: &mut H) {
58        self.get_0().hash(state);
59    }
60}
61
62/// Implements the `PartialOrd` trait for `RoutePattern`.
63///
64/// This allows for partial ordering of `RoutePattern` instances.
65impl PartialOrd for RoutePattern {
66    /// Partially compares two `RoutePattern` instances.
67    ///
68    /// # Arguments
69    ///
70    /// - `&Self`- The other `RoutePattern` instance to compare against.
71    ///
72    /// # Returns
73    ///
74    /// - `Option<Ordering>`- The ordering of the two instances.
75    #[inline(always)]
76    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
77        Some(self.cmp(other))
78    }
79}
80
81/// Implements the `Ord` trait for `RoutePattern`.
82///
83/// This allows for total ordering of `RoutePattern` instances.
84impl Ord for RoutePattern {
85    /// Compares two `RoutePattern` instances.
86    ///
87    /// # Arguments
88    ///
89    /// - `&Self`- The other `RoutePattern` instance to compare against.
90    ///
91    /// # Returns
92    ///
93    /// - `Ordering`- The ordering of the two instances.
94    #[inline(always)]
95    fn cmp(&self, other: &Self) -> Ordering {
96        self.get_0().cmp(other.get_0())
97    }
98}
99
100/// Implements the `PartialEq` trait for `RouteMatcher`.
101///
102/// This allows for comparing two `RouteMatcher` instances for equality.
103impl PartialEq for RouteMatcher {
104    /// Checks if two `RouteMatcher` instances are equal.
105    ///
106    /// # Arguments
107    ///
108    /// - `&Self`- The other `RouteMatcher` instance to compare against.
109    ///
110    /// # Returns
111    ///
112    /// - `bool`- `true` if the instances are equal, `false` otherwise.
113    fn eq(&self, other: &Self) -> bool {
114        if self.get_static_route().len() != other.get_static_route().len() {
115            return false;
116        }
117        for key in self.get_static_route().keys() {
118            if !other.get_static_route().contains_key(key) {
119                return false;
120            }
121        }
122        if self.get_dynamic_route().len() != other.get_dynamic_route().len() {
123            return false;
124        }
125        for (segment_count, routes) in self.get_dynamic_route() {
126            match other.get_dynamic_route().get(segment_count) {
127                Some(other_routes) if routes.len() == other_routes.len() => {
128                    for (pattern, _) in routes {
129                        if !other_routes.iter().any(|(p, _)| p == pattern) {
130                            return false;
131                        }
132                    }
133                }
134                _ => return false,
135            }
136        }
137        if self.get_regex_route().len() != other.get_regex_route().len() {
138            return false;
139        }
140        for (segment_count, routes) in self.get_regex_route() {
141            match other.get_regex_route().get(segment_count) {
142                Some(other_routes) if routes.len() == other_routes.len() => {
143                    for (pattern, _) in routes {
144                        if !other_routes.iter().any(|(p, _)| p == pattern) {
145                            return false;
146                        }
147                    }
148                }
149                _ => return false,
150            }
151        }
152        true
153    }
154}
155
156/// Implements the `Eq` trait for `RouteMatcher`.
157///
158/// This indicates that `RouteMatcher` has a total equality relation.
159impl Eq for RouteMatcher {}
160
161/// Implements the `Eq` trait for `RouteSegment`.
162///
163/// This indicates that `RouteSegment` has a total equality relation.
164impl Eq for RouteSegment {}
165
166/// Implements the `PartialOrd` trait for `RouteSegment`.
167///
168/// This allows for partial ordering of `RouteSegment` instances.
169impl PartialOrd for RouteSegment {
170    /// Partially compares two `RouteSegment` instances.
171    ///
172    /// # Arguments
173    ///
174    /// - `&Self`- The other `RouteSegment` instance to compare against.
175    ///
176    /// # Returns
177    ///
178    /// - `Option<Ordering>`- The ordering of the two instances.
179    #[inline(always)]
180    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
181        Some(self.cmp(other))
182    }
183}
184
185/// Implements the `Ord` trait for `RouteSegment`.
186///
187/// This allows for total ordering of `RouteSegment` instances.
188impl Ord for RouteSegment {
189    /// Compares two `RouteSegment` instances.
190    ///
191    /// # Arguments
192    ///
193    /// - `&Self`- The other `RouteSegment` instance to compare against.
194    ///
195    /// # Returns
196    ///
197    /// - `Ordering`- The ordering of the two instances.
198    #[inline(always)]
199    fn cmp(&self, other: &Self) -> Ordering {
200        match (self, other) {
201            (Self::Static(s1), Self::Static(s2)) => s1.cmp(s2),
202            (Self::Dynamic(d1), Self::Dynamic(d2)) => d1.cmp(d2),
203            (Self::Regex(n1, r1), Self::Regex(n2, r2)) => {
204                n1.cmp(n2).then_with(|| r1.as_str().cmp(r2.as_str()))
205            }
206            (Self::Static(_), _) => Ordering::Less,
207            (_, Self::Static(_)) => Ordering::Greater,
208            (Self::Dynamic(_), _) => Ordering::Less,
209            (_, Self::Dynamic(_)) => Ordering::Greater,
210        }
211    }
212}
213
214/// Implements the `PartialEq` trait for `RouteSegment`.
215///
216/// This allows for comparing two `RouteSegment` instances for equality.
217impl PartialEq for RouteSegment {
218    /// Checks if two `RouteSegment` instances are equal.
219    ///
220    /// # Arguments
221    ///
222    /// - `&Self`- The other `RouteSegment` instance to compare against.
223    ///
224    /// # Returns
225    ///
226    /// - `bool`- `true` if the instances are equal, `false` otherwise.
227    #[inline(always)]
228    fn eq(&self, other: &Self) -> bool {
229        match (self, other) {
230            (Self::Static(l0), Self::Static(r0)) => l0 == r0,
231            (Self::Dynamic(l0), Self::Dynamic(r0)) => l0 == r0,
232            (Self::Regex(l0, l1), Self::Regex(r0, r1)) => l0 == r0 && l1.as_str() == r1.as_str(),
233            _ => false,
234        }
235    }
236}
237
238/// Implements the `Hash` trait for `RouteSegment`.
239///
240/// This allows `RouteSegment` to be used in hash-based collections.
241impl Hash for RouteSegment {
242    /// Hashes the `RouteSegment` instance.
243    ///
244    /// # Arguments
245    ///
246    /// - `&mut HHasher` - The hasher to use.
247    #[inline(always)]
248    fn hash<H: Hasher>(&self, state: &mut H) {
249        match self {
250            Self::Static(s) => {
251                0u8.hash(state);
252                s.hash(state);
253            }
254            Self::Dynamic(d) => {
255                1u8.hash(state);
256                d.hash(state);
257            }
258            Self::Regex(name, regex) => {
259                2u8.hash(state);
260                name.hash(state);
261                regex.as_str().hash(state);
262            }
263        }
264    }
265}
266
267/// Manages route patterns, including parsing and matching.
268///
269/// This struct is responsible for defining and validating route structures,
270/// supporting static, dynamic, and regex-based path matching.
271impl RoutePattern {
272    /// Creates a new RoutePattern by parsing a route string.
273    ///
274    /// # Arguments
275    ///
276    /// - `&str` - The raw route string to parse.
277    ///
278    /// # Returns
279    ///
280    /// - `Result<RoutePattern, RouteError>` - The parsed RoutePattern on success, or RouteError on failure.
281    pub(crate) fn new(route: &str) -> Result<RoutePattern, RouteError> {
282        Ok(Self(Self::parse_route(route)?))
283    }
284
285    /// Parses a raw route string into RouteSegments.
286    ///
287    /// This is the core logic for interpreting the route syntax.
288    ///
289    /// # Arguments
290    ///
291    /// - `&str` - The raw route string.
292    ///
293    /// # Returns
294    ///
295    /// - `Result<RouteSegmentList, RouteError>` - Vector of RouteSegments on success, or RouteError on failure.
296    fn parse_route(route: &str) -> Result<RouteSegmentList, RouteError> {
297        if route.is_empty() {
298            return Err(RouteError::EmptyPattern);
299        }
300        let route: &str = route.trim_start_matches(DEFAULT_HTTP_PATH);
301        if route.is_empty() {
302            return Ok(Vec::new());
303        }
304        let estimated_segments: usize = route.matches(DEFAULT_HTTP_PATH).count() + 1;
305        let mut segments: RouteSegmentList = Vec::with_capacity(estimated_segments);
306        for segment in route.split(DEFAULT_HTTP_PATH) {
307            if segment.starts_with(LEFT_BRACKET) && segment.ends_with(RIGHT_BRACKET) {
308                let content: &str = &segment[1..segment.len() - 1];
309                if let Some((name, pattern)) = content.split_once(COLON) {
310                    match Regex::new(pattern) {
311                        Ok(regex) => {
312                            segments.push(RouteSegment::Regex(name.to_owned(), regex));
313                        }
314                        Err(error) => {
315                            return Err(RouteError::InvalidRegexPattern(format!(
316                                "Invalid regex pattern '{}{}{}",
317                                pattern, COLON, error
318                            )));
319                        }
320                    }
321                } else {
322                    segments.push(RouteSegment::Dynamic(content.to_owned()));
323                }
324            } else {
325                segments.push(RouteSegment::Static(segment.to_owned()));
326            }
327        }
328        Ok(segments)
329    }
330
331    /// Matches this route pattern against a request path.
332    ///
333    /// If the pattern matches, extracts any dynamic or regex parameters.
334    ///
335    /// # Arguments
336    ///
337    /// - `&str` - The request path to match against.
338    ///
339    /// # Returns
340    ///
341    /// - `Option<RouteParams>` - Some with parameters if matched, None otherwise.
342    pub(crate) fn try_match_path(&self, path: &str) -> Option<RouteParams> {
343        let path: &str = path.trim_start_matches(DEFAULT_HTTP_PATH);
344        let route_segments_len: usize = self.get_0().len();
345        let is_tail_regex: bool = matches!(self.get_0().last(), Some(RouteSegment::Regex(_, _)));
346        if path.is_empty() {
347            if route_segments_len == 0 {
348                return Some(hash_map_xx_hash3_64());
349            }
350            return None;
351        }
352        let mut path_segments: PathComponentList = Vec::with_capacity(route_segments_len);
353        let path_bytes: &[u8] = path.as_bytes();
354        let path_separator_byte: u8 = DEFAULT_HTTP_PATH_BYTES[0];
355        let mut segment_start: usize = 0;
356        for (i, &byte) in path_bytes.iter().enumerate() {
357            if byte == path_separator_byte {
358                if segment_start < i {
359                    path_segments.push(&path[segment_start..i]);
360                }
361                segment_start = i + 1;
362            }
363        }
364        if segment_start < path.len() {
365            path_segments.push(&path[segment_start..]);
366        }
367        let path_segments_len: usize = path_segments.len();
368        if (!is_tail_regex && path_segments_len != route_segments_len)
369            || (is_tail_regex && path_segments_len < route_segments_len - 1)
370        {
371            return None;
372        }
373        let mut params: RouteParams = hash_map_xx_hash3_64();
374        for (idx, segment) in self.get_0().iter().enumerate() {
375            match segment {
376                RouteSegment::Static(expected_path) => {
377                    if path_segments.get(idx).copied() != Some(expected_path.as_str()) {
378                        return None;
379                    }
380                }
381                RouteSegment::Dynamic(param_name) => {
382                    params.insert(param_name.clone(), path_segments.get(idx)?.to_string());
383                }
384                RouteSegment::Regex(param_name, regex) => {
385                    let segment_value: String = if idx == route_segments_len - 1 {
386                        path_segments[idx..].join(DEFAULT_HTTP_PATH)
387                    } else {
388                        match path_segments.get(idx) {
389                            Some(val) => val.to_string(),
390                            None => return None,
391                        }
392                    };
393                    if let Some(mat) = regex.find(&segment_value) {
394                        if mat.start() != 0 || mat.end() != segment_value.len() {
395                            return None;
396                        }
397                    } else {
398                        return None;
399                    }
400                    params.insert(param_name.clone(), segment_value);
401                    if idx == route_segments_len - 1 {
402                        break;
403                    }
404                }
405            }
406        }
407        Some(params)
408    }
409
410    /// Checks if the route pattern is static.
411    ///
412    /// # Returns
413    ///
414    /// - `bool` - true if the pattern is static, false otherwise.
415    #[inline(always)]
416    pub(crate) fn is_static(&self) -> bool {
417        self.get_0()
418            .iter()
419            .all(|seg| matches!(seg, RouteSegment::Static(_)))
420    }
421
422    /// Checks if the route pattern is dynamic.
423    ///
424    /// # Returns
425    ///
426    /// - `bool` - true if the pattern is dynamic, false otherwise.
427    #[inline(always)]
428    pub(crate) fn is_dynamic(&self) -> bool {
429        self.get_0()
430            .iter()
431            .any(|seg| matches!(seg, RouteSegment::Dynamic(_)))
432            && self
433                .get_0()
434                .iter()
435                .all(|seg| !matches!(seg, RouteSegment::Regex(_, _)))
436    }
437
438    /// Gets the number of segments in this route pattern.
439    ///
440    /// # Returns
441    ///
442    /// - `usize` - The number of segments.
443    #[inline(always)]
444    pub(crate) fn segment_count(&self) -> usize {
445        self.get_0().len()
446    }
447
448    /// Checks if the last segment is a regex pattern.
449    ///
450    /// # Returns
451    ///
452    /// - `bool` - true if the last segment is a regex, false otherwise.
453    #[inline(always)]
454    pub(crate) fn has_tail_regex(&self) -> bool {
455        matches!(self.get_0().last(), Some(RouteSegment::Regex(_, _)))
456    }
457}
458
459/// Manages a collection of route, enabling efficient lookup and dispatch.
460///
461/// This struct stores route categorized by type (static, dynamic, regex)
462/// to quickly find the appropriate handler for incoming requests.
463impl RouteMatcher {
464    /// Creates a new, empty RouteMatcher.
465    ///
466    /// # Returns
467    ///
468    /// - `RouteMatcher` - A new RouteMatcher instance with empty route stores.
469    #[inline(always)]
470    pub(crate) fn new() -> Self {
471        Self::default()
472    }
473
474    /// Counts the number of segments in a path.
475    ///
476    /// # Arguments
477    ///
478    /// - `&str` - The path to count segments in.
479    ///
480    /// # Returns
481    ///
482    /// - `usize` - The number of segments.
483    #[inline(always)]
484    fn count_path_segments(path: &str) -> usize {
485        let path: &str = path.trim_start_matches(DEFAULT_HTTP_PATH);
486        if path.is_empty() {
487            return 0;
488        }
489        path.matches(DEFAULT_HTTP_PATH).count() + 1
490    }
491
492    /// Adds a new route and its handler to the matcher.
493    ///
494    /// Adds a route handler to the matcher.
495    ///
496    /// This method categorizes the route as static, dynamic, or regex based on its pattern
497    /// and stores it in the appropriate collection.
498    ///
499    /// # Arguments
500    ///
501    /// - `&str` - The route pattern string.
502    /// - `ServerHookHandler` - The boxed route handler.
503    ///
504    /// # Returns
505    ///
506    /// - `Result<(), RouteError>` - Ok on success, or RouteError if pattern is duplicate.
507    pub(crate) fn add(
508        &mut self,
509        pattern: &str,
510        handler: ServerHookHandler,
511    ) -> Result<(), RouteError> {
512        let route_pattern: RoutePattern = RoutePattern::new(pattern)?;
513        if route_pattern.is_static() {
514            if self.get_static_route().contains_key(pattern) {
515                return Err(RouteError::DuplicatePattern(pattern.to_owned()));
516            }
517            self.get_mut_static_route()
518                .insert(pattern.to_string(), handler);
519            return Ok(());
520        }
521        let target_map: &mut ServerHookPatternRoute = if route_pattern.is_dynamic() {
522            self.get_mut_dynamic_route()
523        } else {
524            self.get_mut_regex_route()
525        };
526        let segment_count: usize = route_pattern.segment_count();
527        let routes_for_count: &mut Vec<(RoutePattern, ServerHookHandler)> =
528            target_map.entry(segment_count).or_default();
529        match routes_for_count.binary_search_by(|(p, _)| p.cmp(&route_pattern)) {
530            Ok(_) => return Err(RouteError::DuplicatePattern(pattern.to_owned())),
531            Err(pos) => routes_for_count.insert(pos, (route_pattern, handler)),
532        }
533        Ok(())
534    }
535
536    /// Resolves and executes a route handler.
537    ///
538    /// This method searches for a matching route and executes it if found.
539    /// Finds a matching route handler for the given path.
540    ///
541    /// # Arguments
542    ///
543    /// - `&Context` - The request context.
544    /// - `&str` - The request path to resolve.
545    ///
546    /// # Returns
547    ///
548    /// - `Option<ServerHookHandler>` - The matched route handler if found, None otherwise.
549    pub(crate) async fn try_resolve_route(
550        &self,
551        ctx: &Context,
552        path: &str,
553    ) -> Option<ServerHookHandler> {
554        if let Some(handler) = self.get_static_route().get(path) {
555            ctx.set_route_params(RouteParams::default()).await;
556            return Some(handler.clone());
557        }
558        let path_segment_count: usize = Self::count_path_segments(path);
559        if let Some(routes) = self.get_dynamic_route().get(&path_segment_count) {
560            for (pattern, handler) in routes {
561                if let Some(params) = pattern.try_match_path(path) {
562                    ctx.set_route_params(params).await;
563                    return Some(handler.clone());
564                }
565            }
566        }
567        if let Some(routes) = self.get_regex_route().get(&path_segment_count) {
568            for (pattern, handler) in routes {
569                if let Some(params) = pattern.try_match_path(path) {
570                    ctx.set_route_params(params).await;
571                    return Some(handler.clone());
572                }
573            }
574        }
575        for (&segment_count, routes) in self.get_regex_route() {
576            if segment_count == path_segment_count {
577                continue;
578            }
579            for (pattern, handler) in routes {
580                if pattern.has_tail_regex()
581                    && path_segment_count >= segment_count
582                    && let Some(params) = pattern.try_match_path(path)
583                {
584                    ctx.set_route_params(params).await;
585                    return Some(handler.clone());
586                }
587            }
588        }
589        None
590    }
591}