diff --git a/src/request.rs b/src/request.rs index 854154e6..d572906a 100644 --- a/src/request.rs +++ b/src/request.rs @@ -2,7 +2,7 @@ use async_std::io::{self, prelude::*}; use async_std::task::{Context, Poll}; use routefinder::Captures; -use std::ops::Index; +use std::ops::{Deref, Index}; use std::pin::Pin; #[cfg(feature = "cookies")] @@ -12,6 +12,7 @@ use crate::http::cookies::Cookie; use crate::http::format_err; use crate::http::headers::{self, HeaderName, HeaderValues, ToHeaderValues}; use crate::http::{self, Body, Method, Mime, StatusCode, Url, Version}; +use crate::route::MatchedRoute; use crate::Response; pin_project_lite::pin_project! { @@ -266,6 +267,24 @@ impl Request { &self.state } + /// Get the matched route. + /// + /// Returns the route as a `&str`, borrowed from this `Request`. + /// + /// # Examples + /// + /// ``` + /// # use tide::Request; + /// let mut app = tide::Server::new(); + /// app.at("/route").get(|req: Request<()>| async move { + /// let route = req.route().unwrap_or("No route found"); + /// Ok(route.to_string()) + /// }); + /// ``` + pub fn route(&self) -> Option<&str> { + self.ext::().map(|r| r.deref()) + } + /// Extract and parse a route parameter by name. /// /// Returns the parameter as a `&str`, borrowed from this `Request`. diff --git a/src/route.rs b/src/route.rs index e52889fd..9287ef06 100644 --- a/src/route.rs +++ b/src/route.rs @@ -9,6 +9,18 @@ use crate::{router::Router, Endpoint, Middleware}; use kv_log_macro::trace; +/// Extension struct for storing the matched route in the request. +#[derive(Debug)] +pub(crate) struct MatchedRoute(pub(crate) String); + +impl std::ops::Deref for MatchedRoute { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + /// A handle to a route. /// /// All HTTP requests are made against resources. After using [`Server::at`] (or diff --git a/src/router.rs b/src/router.rs index 70b1ab35..288f80f2 100644 --- a/src/router.rs +++ b/src/router.rs @@ -27,6 +27,7 @@ impl std::fmt::Debug for Router { pub(crate) struct Selection<'a, State> { pub(crate) endpoint: &'a DynEndpoint, pub(crate) params: Captures<'static, 'static>, + pub(crate) matched_route: Option, } impl Router { @@ -63,11 +64,13 @@ impl Router { Selection { endpoint: m.handler(), params: m.captures().into_owned(), + matched_route: Some(m.route().to_string()), } } else if let Some(m) = self.all_method_router.best_match(path) { Selection { endpoint: m.handler(), params: m.captures().into_owned(), + matched_route: Some(m.route().to_string()), } } else if method == http_types::Method::Head { // If it is a HTTP HEAD request then check if there is a callback in the endpoints map @@ -85,11 +88,13 @@ impl Router { Selection { endpoint: &method_not_allowed, params: Captures::default(), + matched_route: None, } } else { Selection { endpoint: ¬_found_endpoint, params: Captures::default(), + matched_route: None, } } } diff --git a/src/server.rs b/src/server.rs index 3b09fc40..faf4c05b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -8,6 +8,7 @@ use kv_log_macro::{info, trace}; use crate::cookies; use crate::listener::{Listener, ToListener}; use crate::middleware::{Middleware, Next}; +use crate::route::MatchedRoute; use crate::router::{Router, Selection}; use crate::{Endpoint, Request, Route}; @@ -287,9 +288,17 @@ where } = self.clone(); let method = req.method().to_owned(); - let Selection { endpoint, params } = router.route(req.url().path(), method); + let Selection { + endpoint, + params, + matched_route, + } = router.route(req.url().path(), method); let route_params = vec![params]; - let req = Request::new(state, req, route_params); + let mut req = Request::new(state, req, route_params); + + if let Some(route) = matched_route { + req.set_ext(MatchedRoute(route)); + } let next = Next { endpoint, @@ -349,9 +358,17 @@ impl