diff --git a/cot/src/router.rs b/cot/src/router.rs index 4c733e249..2d49c26ad 100644 --- a/cot/src/router.rs +++ b/cot/src/router.rs @@ -527,6 +527,9 @@ pub fn split_view_name(view_name: &str) -> (Option<&str>, &str) { /// A route that can be used to route requests to their respective views. /// +/// Non-empty route paths may omit the leading slash. Cot normalizes them by +/// prepending `/`, so `"home"` and `"/home"` define the same route. +/// /// # Examples /// /// ``` @@ -562,7 +565,8 @@ impl Route { /// # unimplemented!() /// } /// - /// let route = Route::with_handler("/", home); + /// let route = Route::with_handler("home", home); + /// assert_eq!(route.url(), "/home"); /// ``` #[must_use] pub fn with_handler(url: &str, handler: H) -> Self @@ -1130,6 +1134,21 @@ mod tests { assert_eq!(response.status(), StatusCode::OK); } + #[cot::test] + async fn router_route_without_leading_slash() { + let route = Route::with_handler_and_name("test", MockHandler, "test"); + assert_eq!(route.url(), "/test"); + + let router = Router::with_urls(vec![route]); + let response = router.route(test_request(), "/test").await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + + let url = router + .reverse(None, "test", &ReverseParamMap::new()) + .unwrap(); + assert_eq!(url, "/test"); + } + #[cot::test] async fn router_handle() { let route = Route::with_handler("/test", MockHandler); diff --git a/cot/src/router/path.rs b/cot/src/router/path.rs index ba1f6fd67..cd2507992 100644 --- a/cot/src/router/path.rs +++ b/cot/src/router/path.rs @@ -25,7 +25,10 @@ impl PathMatcher { Param { start: usize }, } - let path_pattern = path_pattern.into(); + let mut path_pattern = path_pattern.into(); + if !path_pattern.is_empty() && !path_pattern.starts_with('/') { + path_pattern.insert(0, '/'); + } let mut parts = Vec::new(); let mut state = State::Literal { start: 0 }; @@ -349,6 +352,20 @@ mod tests { assert_eq!(path_parser.capture("/test"), None); } + #[test] + fn path_parser_adds_missing_leading_slash() { + let path_parser = PathMatcher::new("users/{id}"); + let mut params = ReverseParamMap::new(); + params.insert("id", "123"); + + assert_eq!( + path_parser.capture("/users/123"), + Some(CaptureResult::new(vec![PathParam::new("id", "123")], "")) + ); + assert_eq!(path_parser.reverse(¶ms).unwrap(), "/users/123"); + assert_eq!(path_parser.to_string(), "/users/{id}"); + } + #[test] fn path_parser_escaped() { let path_parser = PathMatcher::new("/users/{{{{{{escaped}}}}}}");