path_tree/
lib.rs

1//! path-tree is a lightweight high performance HTTP request router for Rust.
2//!
3//! # Example
4//!
5//! ```
6//! use path_tree::PathTree;
7//!
8//! /*
9//! / •0
10//! ├── api/
11//! │   └── + •13
12//! ├── login •1
13//! ├── public/
14//! │   └── ** •7
15//! ├── s
16//! │   ├── ettings •3
17//! │   │   └── /
18//! │   │       └── : •4
19//! │   └── ignup •2
20//! └── : •5
21//!     └── /
22//!         └── : •6
23//!             └── /
24//!                 ├── actions/
25//!                 │   └── :
26//!                 │       └── \:
27//!                 │           └── : •10
28//!                 ├── releases/download/
29//!                 │   └── :
30//!                 │       └── /
31//!                 │           └── :
32//!                 │               └── .
33//!                 │                   └── : •8
34//!                 ├── tags/
35//!                 │   └── :
36//!                 │       └── -
37//!                 │           └── :
38//!                 │               └── -
39//!                 │                   └── : •9
40//!                 ├── : •11
41//!                 └── ** •12
42//! */
43//! let mut tree = PathTree::new();
44//!
45//! tree.insert("/", 0);
46//! tree.insert("/login", 1);
47//! tree.insert("/signup", 2);
48//! tree.insert("/settings", 3);
49//! tree.insert("/settings/:page", 4);
50//! tree.insert("/:user", 5);
51//! tree.insert("/:user/:repo", 6);
52//! tree.insert("/public/:any*", 7);
53//! tree.insert("/:org/:repo/releases/download/:tag/:filename.:ext", 8);
54//! tree.insert("/:org/:repo/tags/:day-:month-:year", 9);
55//! tree.insert("/:org/:repo/actions/:name\\::verb", 10);
56//! tree.insert("/:org/:repo/:page", 11);
57//! tree.insert("/:org/:repo/*", 12);
58//! tree.insert("/api/+", 13);
59//!
60//! let (h, p) = tree.find("/").unwrap();
61//! assert_eq!(h, &0);
62//! assert_eq!(p.params(), vec![]);
63//!
64//! let (h, p) = tree.find("/login").unwrap();
65//! assert_eq!(h, &1);
66//! assert_eq!(p.params(), vec![]);
67//!
68//! let (h, p) = tree.find("/settings/admin").unwrap();
69//! assert_eq!(h, &4);
70//! assert_eq!(p.params(), vec![("page", "admin")]);
71//!
72//! let (h, p) = tree.find("/viz-rs").unwrap();
73//! assert_eq!(h, &5);
74//! assert_eq!(p.params(), vec![("user", "viz-rs")]);
75//!
76//! let (h, p) = tree.find("/viz-rs/path-tree").unwrap();
77//! assert_eq!(h, &6);
78//! assert_eq!(p.params(), vec![("user", "viz-rs"), ("repo", "path-tree")]);
79//!
80//! let (h, p) = tree.find("/rust-lang/rust-analyzer/releases/download/2022-09-12/rust-analyzer-aarch64-apple-darwin.gz").unwrap();
81//! assert_eq!(h, &8);
82//! assert_eq!(
83//!     p.params(),
84//!     vec![
85//!         ("org", "rust-lang"),
86//!         ("repo", "rust-analyzer"),
87//!         ("tag", "2022-09-12"),
88//!         ("filename", "rust-analyzer-aarch64-apple-darwin"),
89//!         ("ext", "gz")
90//!     ]
91//! );
92//!
93//! let (h, p) = tree.find("/rust-lang/rust-analyzer/tags/2022-09-12").unwrap();
94//! assert_eq!(h, &9);
95//! assert_eq!(
96//!     p.params(),
97//!     vec![
98//!         ("org", "rust-lang"),
99//!         ("repo", "rust-analyzer"),
100//!         ("day", "2022"),
101//!         ("month", "09"),
102//!         ("year", "12")
103//!     ]
104//! );
105//!
106//! let (h, p) = tree.find("/rust-lang/rust-analyzer/actions/ci:bench").unwrap();
107//! assert_eq!(h, &10);
108//! assert_eq!(
109//!     p.params(),
110//!     vec![
111//!         ("org", "rust-lang"),
112//!         ("repo", "rust-analyzer"),
113//!         ("name", "ci"),
114//!         ("verb", "bench"),
115//!     ]
116//! );
117//!
118//! let (h, p) = tree.find("/rust-lang/rust-analyzer/stargazers").unwrap();
119//! assert_eq!(h, &11);
120//! assert_eq!(p.params(), vec![("org", "rust-lang"), ("repo", "rust-analyzer"), ("page", "stargazers")]);
121//!
122//! let (h, p) = tree.find("/rust-lang/rust-analyzer/stargazers/404").unwrap();
123//! assert_eq!(h, &12);
124//! assert_eq!(p.params(), vec![("org", "rust-lang"), ("repo", "rust-analyzer"), ("*1", "stargazers/404")]);
125//!
126//! let (h, p) = tree.find("/public/js/main.js").unwrap();
127//! assert_eq!(h, &7);
128//! assert_eq!(p.params(), vec![("any", "js/main.js")]);
129//!
130//! let (h, p) = tree.find("/api/v1").unwrap();
131//! assert_eq!(h, &13);
132//! assert_eq!(p.params(), vec![("+1", "v1")]);
133//! ```
134
135#![no_std]
136#![forbid(unsafe_code)]
137#![warn(rust_2018_idioms, unreachable_pub)]
138
139extern crate alloc;
140
141use alloc::{
142    string::{String, ToString},
143    vec::Vec,
144};
145use core::{slice::Iter, str::from_utf8};
146use smallvec::SmallVec;
147
148mod node;
149pub use node::{Key, Node};
150
151mod parser;
152pub use parser::{Kind, Parser, Piece, Position};
153
154/// A path tree.
155#[derive(Clone, Debug)]
156pub struct PathTree<T> {
157    id: usize,
158    routes: Vec<(T, Vec<Piece>)>,
159    pub node: Node<usize>,
160}
161
162impl<T> Default for PathTree<T> {
163    fn default() -> Self {
164        Self::new()
165    }
166}
167
168impl<T> PathTree<T> {
169    /// Creates a new [`PathTree`].
170    #[must_use]
171    pub fn new() -> Self {
172        Self {
173            id: 0,
174            routes: Vec::new(),
175            node: Node::new(Key::String(Vec::new()), None),
176        }
177    }
178
179    /// Inserts a part path-value to the tree and returns the id.
180    #[must_use]
181    pub fn insert(&mut self, path: &str, value: T) -> usize {
182        let mut node = &mut self.node;
183
184        let (overwritten, pieces) = if path.is_empty() {
185            (false, Vec::new())
186        } else {
187            let pieces = Parser::new(path).collect::<Vec<_>>();
188            node = pieces.iter().fold(node, |node, piece| match piece {
189                Piece::String(s) => node.insert_bytes(&s[..]),
190                Piece::Parameter(_, k) => node.insert_parameter(*k),
191            });
192            (true, pieces)
193        };
194
195        if let Some(id) = node.value {
196            self.routes[id].0 = value;
197            if overwritten {
198                self.routes[id].1 = pieces;
199            }
200            id
201        } else {
202            self.routes.push((value, pieces));
203            let id = self.id;
204            node.value = Some(id);
205            self.id += 1;
206            id
207        }
208    }
209
210    /// Returns the [`Path`] by the given path.
211    #[must_use]
212    pub fn find<'a, 'b>(&'a self, path: &'b str) -> Option<(&'a T, Path<'a, 'b>)> {
213        let bytes = path.as_bytes();
214        self.node.find(bytes).and_then(|(id, ranges)| {
215            self.routes.get(*id).map(|(value, pieces)| {
216                (
217                    value,
218                    Path {
219                        id,
220                        pieces,
221                        // opt!
222                        raws: ranges
223                            .into_iter()
224                            .filter_map(|r| from_utf8(&bytes[r]).ok())
225                            .rev()
226                            .collect(),
227                    },
228                )
229            })
230        })
231    }
232
233    /// Gets the route by id.
234    #[must_use]
235    #[inline]
236    pub fn get_route(&self, index: usize) -> Option<&(T, Vec<Piece>)> {
237        self.routes.get(index)
238    }
239
240    /// Generates URL with the params.
241    #[must_use]
242    pub fn url_for(&self, index: usize, params: &[&str]) -> Option<String> {
243        self.get_route(index).and_then(|(_, pieces)| {
244            let mut bytes = Vec::new();
245            let mut iter = params.iter();
246
247            pieces.iter().for_each(|piece| match piece {
248                Piece::String(s) => {
249                    bytes.extend_from_slice(s);
250                }
251                Piece::Parameter(_, _) => {
252                    if let Some(s) = iter.next() {
253                        bytes.extend_from_slice(s.as_bytes());
254                    }
255                }
256            });
257
258            from_utf8(&bytes).map(ToString::to_string).ok()
259        })
260    }
261
262    pub fn iter(&self) -> Iter<'_, (T, Vec<Piece>)> {
263        self.routes.iter()
264    }
265}
266
267impl<'a, T> IntoIterator for &'a PathTree<T> {
268    type Item = &'a (T, Vec<Piece>);
269    type IntoIter = Iter<'a, (T, Vec<Piece>)>;
270
271    fn into_iter(self) -> Self::IntoIter {
272        self.iter()
273    }
274}
275
276/// Matched route path infomation.
277#[derive(Clone, Debug, PartialEq, Eq)]
278pub struct Path<'a, 'b> {
279    pub id: &'a usize,
280    pub pieces: &'a [Piece],
281    pub raws: SmallVec<[&'b str; 4]>,
282}
283
284impl Path<'_, '_> {
285    /// Gets current path pattern.
286    ///
287    /// # Panics
288    ///
289    /// Will panic if bytes to string conversion fails.
290    pub fn pattern(&self) -> String {
291        let mut bytes = Vec::new();
292
293        self.pieces.iter().for_each(|piece| match piece {
294            Piece::String(s) => {
295                if s == b":" || s == b"+" || s == b"?" {
296                    bytes.push(b'\\');
297                }
298                bytes.extend_from_slice(s);
299            }
300            Piece::Parameter(p, k) => match p {
301                Position::Index(_, _) => {
302                    if *k == Kind::OneOrMore {
303                        bytes.push(b'+');
304                    } else if *k == Kind::ZeroOrMore || *k == Kind::ZeroOrMoreSegment {
305                        bytes.push(b'*');
306                    }
307                }
308                Position::Named(n) => match k {
309                    Kind::Normal | Kind::Optional | Kind::OptionalSegment => {
310                        bytes.push(b':');
311                        bytes.extend_from_slice(n);
312                        if *k == Kind::Optional || *k == Kind::OptionalSegment {
313                            bytes.push(b'?');
314                        }
315                    }
316                    Kind::OneOrMore => {
317                        bytes.push(b'+');
318                        bytes.extend_from_slice(n);
319                    }
320                    Kind::ZeroOrMore | Kind::ZeroOrMoreSegment => {
321                        bytes.push(b'*');
322                        bytes.extend_from_slice(n);
323                    }
324                },
325            },
326        });
327
328        from_utf8(&bytes)
329            .map(ToString::to_string)
330            .expect("pattern generated failure")
331    }
332
333    /// Returns the parameters of the current path.
334    #[must_use]
335    pub fn params(&self) -> Vec<(&str, &str)> {
336        self.params_iter().collect()
337    }
338
339    /// Returns the parameters iterator of the current path.
340    pub fn params_iter(&self) -> impl Iterator<Item = (&str, &str)> {
341        #[inline]
342        fn piece_filter(piece: &Piece) -> Option<&str> {
343            match piece {
344                Piece::String(_) => None,
345                Piece::Parameter(p, _) => from_utf8(match p {
346                    Position::Index(_, n) | Position::Named(n) => n,
347                })
348                .ok(),
349            }
350        }
351
352        self.pieces
353            .iter()
354            .filter_map(piece_filter)
355            .zip(self.raws.iter().copied())
356    }
357}