gotham/router/tree/
mod.rs

1//! Defines a hierarchial `Tree` with subtrees of `Node`.
2
3use crate::helpers::http::PercentDecoded;
4use crate::router::route::Route;
5use crate::router::tree::node::Node;
6use crate::router::tree::segment::{SegmentMapping, SegmentType};
7use hyper::Body;
8use log::trace;
9
10pub mod node;
11pub mod regex;
12pub mod segment;
13
14/// A hierarchical structure that provides a root `Node` and subtrees of linked nodes
15/// that represent valid `Request` paths.
16///
17/// The `Tree` is created by the `gotham::router::builder` API and used internally by the `Router`
18/// to determine the valid `Route` instances for a request path before dispatch.
19pub struct Tree {
20    root: Node,
21}
22
23impl Tree {
24    /// Creates a new `Tree` and root `Node`.
25    pub fn new() -> Self {
26        trace!(" creating new tree");
27        Tree {
28            root: Node::new("/", SegmentType::Static),
29        }
30    }
31
32    /// Adds a direct child to the root of the `Tree`.
33    pub fn add_child(&mut self, child: Node) {
34        self.root.add_child(child);
35    }
36
37    /// Adds a `Route` be evaluated by the `Router` when the root of the `Tree` is requested.
38    pub fn add_route(&mut self, route: Box<dyn Route<ResBody = Body> + Send + Sync>) {
39        self.root.add_route(route);
40    }
41
42    /// Borrow the root `NodeBuilder` as mutable.
43    pub fn borrow_root_mut(&mut self) -> &mut Node {
44        &mut self.root
45    }
46
47    /// Determines if a child `Node` representing the exact segment provided exists at the root of
48    /// the `Tree`.
49    ///
50    /// To be used in building a `Tree` structure only.
51    pub fn has_child(&self, segment: &str, segment_type: SegmentType) -> bool {
52        self.root.has_child(segment, segment_type)
53    }
54
55    /// Attempt to acquire a path from the `Tree` which matches the `Request` path and is routable.
56    pub(crate) fn traverse<'a>(
57        &'a self,
58        req_path_segments: &'a [PercentDecoded],
59    ) -> Option<(&Node, SegmentMapping<'a>, usize)> {
60        trace!(" starting tree traversal");
61        self.root.match_node(req_path_segments)
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use hyper::{Method, Response, StatusCode};
68
69    use crate::extractor::{NoopPathExtractor, NoopQueryStringExtractor};
70    use crate::helpers::http::request::path::RequestPathSegments;
71    use crate::helpers::http::response::create_empty_response;
72    use crate::pipeline::{finalize_pipeline_set, new_pipeline_set};
73    use crate::router::route::dispatch::DispatcherImpl;
74    use crate::router::route::matcher::MethodOnlyRouteMatcher;
75    use crate::router::route::{Delegation, Extractors, RouteImpl};
76    use crate::state::State;
77
78    use super::*;
79
80    fn handler(state: State) -> (State, Response<Body>) {
81        let res = create_empty_response(&state, StatusCode::OK);
82        (state, res)
83    }
84
85    #[test]
86    fn tree_traversal_tests() {
87        let pipeline_set = finalize_pipeline_set(new_pipeline_set());
88        let mut tree = Tree::new();
89
90        let mut activate_node_builder = Node::new("activate", SegmentType::Static);
91
92        let mut thing_node_builder = Node::new("thing", SegmentType::Dynamic);
93        let thing_route = {
94            let methods = vec![Method::GET];
95            let matcher = MethodOnlyRouteMatcher::new(methods);
96            let dispatcher = Box::new(DispatcherImpl::new(|| Ok(handler), (), pipeline_set));
97            let extractors: Extractors<NoopPathExtractor, NoopQueryStringExtractor> =
98                Extractors::new();
99            let route = RouteImpl::new(matcher, dispatcher, extractors, Delegation::Internal);
100            Box::new(route)
101        };
102        thing_node_builder.add_route(thing_route);
103
104        activate_node_builder.add_child(thing_node_builder);
105        tree.add_child(activate_node_builder);
106
107        let request_path_segments = RequestPathSegments::new("/%61ctiv%61te/workflow5");
108        match tree.traverse(request_path_segments.segments().as_slice()) {
109            Some((node, params, processed)) => {
110                assert!(node.is_routable());
111                assert_eq!(processed, 2);
112                assert_eq!(
113                    params.get("thing").unwrap().last().unwrap().as_ref(),
114                    "workflow5"
115                );
116            }
117            None => panic!(),
118        }
119
120        assert!(tree
121            .traverse(&[PercentDecoded::new("/").unwrap()])
122            .is_none());
123        assert!(tree
124            .traverse(&[PercentDecoded::new("/activate").unwrap()])
125            .is_none());
126    }
127}