Skip to content

Commit fa0f7d6

Browse files
author
Gameldar
committed
Split the implementation to add and add_check
add calls add_check and unwraps it Also added a bit of documentation around the router and the add methods
1 parent 5843a16 commit fa0f7d6

File tree

1 file changed

+214
-1
lines changed

1 file changed

+214
-1
lines changed

src/lib.rs

Lines changed: 214 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,177 @@ impl<T> Match<T> {
133133
}
134134
}
135135

136+
/// A Router for defining matching rules (``routes``) for paths to a destination (``handler``).
137+
/// One or more routes are added to the router and then paths can be recognized and a match
138+
/// returned. Routes can contain parameters that are then returned as part of the match.
139+
///
140+
/// # Example
141+
///
142+
/// ```
143+
/// use route_recognizer::Router;
144+
///
145+
/// #[derive(PartialEq)]
146+
/// enum FooBarBaz {
147+
/// FOO,
148+
/// BAR,
149+
/// BAZ,
150+
/// };
151+
///
152+
/// let mut router = Router::new();
153+
/// router.add("/foo", FooBarBaz::FOO);
154+
/// router.add("/foo/:bar", FooBarBaz::BAR);
155+
/// router.add("/foo/:bar/*baz", FooBarBaz::BAZ);
156+
///
157+
/// let m = router.recognize("/foo").unwrap();
158+
/// if *m.handler == FooBarBaz::FOO {
159+
/// println!("do some foo");
160+
/// }
161+
///
162+
/// let m = router.recognize("/foo/123").unwrap();
163+
/// if *m.handler == FooBarBaz::BAR {
164+
/// println!("Got a bar of {}", m.params["bar"]);
165+
/// }
166+
///
167+
/// let m = router.recognize("/foo/123/abc/def").unwrap();
168+
/// if *m.handler == FooBarBaz::BAZ {
169+
/// println!("Got a bar of {} and a baz of {}", m.params["bar"], m.params["baz"]);
170+
/// }
171+
/// ```
172+
///
173+
///
174+
/// # Route types
175+
///
176+
/// A ``route`` consists of one or more segments, separated by a ``/``, to be matched against the ``path`` to be
177+
/// recognized. There are three types of segments - *static*, *dynamic*, *star*:
178+
///
179+
/// 1. *static* - a specific string to match
180+
///
181+
/// ```
182+
/// use route_recognizer::Router;
183+
/// let mut router = Router::new();
184+
/// router.add("/foo", "foo".to_string());
185+
/// router.add("/foo/bar", "foobar".to_string());
186+
///
187+
/// let m = router.recognize("/foo").unwrap();
188+
/// assert_eq!(*m.handler, "foo"); // foo is matched
189+
///
190+
/// let m = router.recognize("/foo/bar").unwrap();
191+
/// assert_eq!(*m.handler, "foobar"); // foobar is matched
192+
///
193+
/// let m = router.recognize("/foo/bar/baz");
194+
/// assert!(m.is_err()); // No match is found
195+
/// ```
196+
///
197+
/// 2. *dynamic* - a single segment is matched. Dynamic segments start with a ``:`` and can
198+
/// be named to be retrieved as a parameter.
199+
///
200+
/// ```
201+
/// use route_recognizer::Router;
202+
/// let mut router = Router::new();
203+
/// router.add("/foo/:bar", "foobar".to_string());
204+
/// router.add("/foo/:bar/baz", "foobarbaz".to_string());
205+
///
206+
/// let m = router.recognize("/foo");
207+
/// assert!(m.is_err()); // No match is found
208+
///
209+
/// let m = router.recognize("/foo/bar").unwrap();
210+
/// assert_eq!(*m.handler, "foobar"); // foobar is matched
211+
/// assert_eq!(m.params["bar"], "bar"); // parameter 'bar' is set to 'bar'
212+
///
213+
/// let m = router.recognize("/foo/123").unwrap();
214+
/// assert_eq!(*m.handler, "foobar"); // foobar is matched
215+
/// assert_eq!(m.params["bar"], "123"); // parameter 'bar' is set to '123'
216+
///```
217+
///
218+
/// 3. *star* - matches one or more segments until the end of the path or another
219+
/// defined segment is reached.
220+
///
221+
/// ```
222+
/// use route_recognizer::Router;
223+
/// let mut router = Router::new();
224+
/// router.add("/foo/*bar", "foobar".to_string());
225+
/// router.add("/foo/*bar/baz", "foobarbaz".to_string());
226+
///
227+
/// let m = router.recognize("/foo");
228+
/// assert!(m.is_err()); // No match is found
229+
///
230+
/// let m = router.recognize("/foo/123").unwrap();
231+
/// assert_eq!(*m.handler, "foobar"); // foobar is matched
232+
/// assert_eq!(m.params["bar"], "123"); // parameter 'bar' is set to '123'
233+
///
234+
/// let m = router.recognize("/foo/123/abc/def").unwrap();
235+
/// assert_eq!(*m.handler, "foobar"); // foobar is matched
236+
/// assert_eq!(m.params["bar"], "123/abc/def"); // parameter 'bar' is set to '123/abc/def'
237+
///
238+
/// let m = router.recognize("/foo/123/abc/baz").unwrap();
239+
/// assert_eq!(*m.handler, "foobarbaz"); // foobar is matched
240+
/// assert_eq!(m.params["bar"], "123/abc"); // parameter 'bar' is set to '123/abc'
241+
///```
242+
///
243+
/// # Unnamed parameters
244+
///
245+
/// Parameters do not need to have a name, but can be indicated just by the leading ``:``
246+
/// or ``*``. If a name is not defined then the parameter is not captured in the ``params``
247+
/// field of the match.
248+
///
249+
/// For example:
250+
///
251+
/// ```
252+
/// use route_recognizer::Router;
253+
/// let mut router = Router::new();
254+
/// router.add("/foo/*", "foo".to_string());
255+
/// router.add("/bar/:/baz", "barbaz".to_string());
256+
///
257+
/// let m = router.recognize("/foo/123").unwrap();
258+
/// assert_eq!(*m.handler, "foo"); // foo is matched
259+
/// assert_eq!(m.params.iter().next(), None); // but no parameters are found
260+
///
261+
/// let m = router.recognize("/bar/123/baz").unwrap();
262+
/// assert_eq!(*m.handler, "barbaz"); // barbaz is matched
263+
/// assert_eq!(m.params.iter().next(), None); // but no parameters are found
264+
/// ```
265+
///
266+
/// # Routing precedence
267+
///
268+
/// Routes can be a combination of all three types and the most specific match will be
269+
/// the result of the precedence of the types where *static* takes precedence over
270+
/// *dynamic*, which in turn takes precedence over *star* segments. For example, if you
271+
/// have the following three routes:
272+
///
273+
/// ```
274+
/// use route_recognizer::Router;
275+
/// let mut router = Router::new();
276+
/// router.add("/foo", "foo".to_string());
277+
/// router.add("/:bar", "bar".to_string());
278+
/// router.add("/*baz", "baz".to_string());
279+
///
280+
/// let m = router.recognize("/foo").unwrap();
281+
/// assert_eq!(*m.handler, "foo"); // foo is matched as it is a static match
282+
///
283+
/// let m = router.recognize("/123").unwrap();
284+
/// assert_eq!(*m.handler, "bar"); // bar is matched as it is a single segment match,
285+
/// // whereas baz is a star match
286+
/// ```
287+
///
288+
/// The precedence rules also apply within a route itself. So if you have a mix of types
289+
/// the static and dynamic parts will take precedence over star rules. For example:
290+
///
291+
/// ```
292+
/// use route_recognizer::Router;
293+
/// let mut router = Router::new();
294+
/// router.add("/foo/*bar/baz/:bay", "foobarbazbay".to_string());
295+
///
296+
/// let m = router.recognize("/foo/123/abc/def/baz/xyz").unwrap();
297+
/// assert_eq!(m.params["bar"], "123/abc/def");
298+
/// assert_eq!(m.params["bay"], "xyz");
299+
///
300+
/// // note that the match will take the right most match when
301+
/// // a star segment is define, so in a path that contains multiple
302+
/// // baz segments it will match on the last one
303+
/// let m = router.recognize("/foo/123/baz/abc/def/baz/xyz").unwrap();
304+
/// assert_eq!(m.params["bar"], "123/baz/abc/def");
305+
/// assert_eq!(m.params["bay"], "xyz");
306+
/// ```
136307
#[derive(Clone)]
137308
pub struct Router<T> {
138309
nfa: NFA<Metadata>,
@@ -147,7 +318,49 @@ impl<T> Router<T> {
147318
}
148319
}
149320

150-
pub fn add(&mut self, mut route: &str, dest: T) -> Result<(), String> {
321+
/// add a route to the router.
322+
///
323+
/// # Examples
324+
/// Basic usage:
325+
/// ```
326+
/// use route_recognizer::Router;
327+
/// let mut router = Router::new();
328+
/// router.add("/foo/*bar/baz/:bay", "foo".to_string());
329+
/// ```
330+
///
331+
/// # Panics
332+
///
333+
/// If a duplicate name is detected in the route the function will panic to ensure that data
334+
/// is not lost when a route is recognized. If the earlier parameter is not required an unamed
335+
/// parameter (e.g. ``/a/:/:b`` or ``/a/*/:b``) can be used.
336+
///
337+
/// If user defined data is being added as a route, consider using [`Router::add_check`] instead.
338+
///
339+
/// [`Router::add_check`]: struct.Router.html#method.add_check
340+
///
341+
pub fn add(&mut self, route: &str, dest: T) {
342+
self.add_check(route, dest).unwrap();
343+
}
344+
345+
/// add a route to the router returning a result indicating success or failure.
346+
///
347+
/// # Examples
348+
/// Basic usage:
349+
/// ```
350+
/// use route_recognizer::Router;
351+
/// let mut router = Router::new();
352+
/// router.add_check("/foo/*bar/baz/:bay", "foo".to_string()).expect("Failed to add route.");
353+
/// ```
354+
///
355+
/// If duplicate parameter names are defined in the route then an ``Error`` is returned:
356+
/// ```
357+
/// let mut router = route_recognizer::Router::new();
358+
/// let result = router.add_check("/foo/:bar/abc/:bar", "foobarabcbar".to_string());
359+
/// assert!(result.is_err());
360+
/// assert_eq!("Duplicate name 'bar' in route foo/:bar/abc/:bar", result.err().unwrap());
361+
/// ```
362+
///
363+
pub fn add_check(&mut self, mut route: &str, dest: T) -> Result<(), String> {
151364
if !route.is_empty() && route.as_bytes()[0] == b'/' {
152365
route = &route[1..];
153366
}

0 commit comments

Comments
 (0)