Skip to content

Commit 7b6ea9a

Browse files
committed
Implement tracing through the list of routes
1 parent 422945d commit 7b6ea9a

File tree

2 files changed

+84
-4
lines changed

2 files changed

+84
-4
lines changed

core/lib/src/local/response.rs

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,9 @@ macro_rules! pub_response_impl {
180180
self._into_msgpack() $(.$suffix)?
181181
}
182182

183-
/// Checks if a route was routed by a specific route type. This only returns true if the route
184-
/// actually generated a response, and a catcher was not run.
183+
/// Checks if a response was generted by a specific route type. This only returns true if the route
184+
/// actually generated the response, and a catcher was _not_ run. See [`was_attempted_by`] to
185+
/// check if a route was attempted, but may not have generated the response
185186
///
186187
/// # Example
187188
///
@@ -202,13 +203,69 @@ macro_rules! pub_response_impl {
202203
/// return a static file can be checked against it. Libraries which provide custom Routes should
203204
/// implement `RouteType`, see [`RouteType`](crate::route::RouteType) for more information.
204205
pub fn was_routed_by<T: crate::route::RouteType>(&self) -> bool {
205-
if let Some(route_type) = self._request().route().map(|r| r.route_type).flatten() {
206+
// If this request was caught, the route in `.route()` did NOT generate this response.
207+
if self._request().catcher().is_some() {
208+
false
209+
} else if let Some(route_type) = self._request().route().map(|r| r.route_type).flatten() {
206210
route_type == std::any::TypeId::of::<T>()
207211
} else {
208212
false
209213
}
210214
}
211215

216+
/// Checks if a request was routed to a specific route type. This will return true for routes
217+
/// that were attempted, _but not actually called_. This enables a test to verify that a route
218+
/// was attempted, even if another route actually generated the response, e.g. an
219+
/// authenticated route will typically defer to an error catcher if the request does not have
220+
/// the proper authentication. This makes it possible to verify that a request was routed to
221+
/// the authentication route, even if the response was eventaully generated by another route or
222+
/// a catcher.
223+
///
224+
/// # Example
225+
///
226+
// WARNING: this doc-test is NOT run, because cargo test --doc does not run doc-tests for items
227+
// only available during tests.
228+
/// ```rust
229+
/// # use rocket::{get, routes, async_trait, request::{Request, Outcome, FromRequest}};
230+
/// # struct WillFail {}
231+
/// # #[async_trait]
232+
/// # impl<'r> FromRequest<'r> for WillFail {
233+
/// # type Error = ();
234+
/// # async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
235+
/// # Outcome::Forward(())
236+
/// # }
237+
/// # }
238+
/// #[get("/", rank = 2)]
239+
/// fn index1(guard: WillFail) -> &'static str { "Hello World" }
240+
/// #[get("/")]
241+
/// fn index2() -> &'static str { "Hello World" }
242+
#[doc = $doc_prelude]
243+
/// # Client::_test_with(|r| r.mount("/", routes![index1, index2]), |_, _, response| {
244+
/// let response: LocalResponse = response;
245+
/// assert!(response.was_attempted_by::<index1>());
246+
/// assert!(response.was_attempted_by::<index2>());
247+
/// assert!(response.was_routed_by::<index2>());
248+
/// # });
249+
/// ```
250+
///
251+
/// # Other Route types
252+
///
253+
/// [`FileServer`](crate::fs::FileServer) implementes `RouteType`, so a route that should
254+
/// return a static file can be checked against it. Libraries which provide custom Routes should
255+
/// implement `RouteType`, see [`RouteType`](crate::route::RouteType) for more information.
256+
///
257+
/// # Note
258+
///
259+
/// This method is marked as `cfg(test)`, and is therefore only available in unit and
260+
/// integration tests. This is because the list of routes attempted is only collected in these
261+
/// testing environments, to minimize performance impacts during normal operation.
262+
#[cfg(test)]
263+
pub fn was_attempted_by<T: crate::route::RouteType>(&self) -> bool {
264+
self._request().route_path(|path| path.iter().any(|r|
265+
r.route_type == Some(std::any::TypeId::of::<T>())
266+
))
267+
}
268+
212269
/// Checks if a route was caught by a specific route type
213270
///
214271
/// # Example

core/lib/src/request/request.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ use crate::http::uncased::UncasedStr;
1919
use crate::http::private::Certificates;
2020
use crate::http::uri::{fmt::Path, Origin, Segments, Host, Authority};
2121

22+
#[cfg(test)]
23+
use parking_lot::Mutex;
24+
2225
/// The type of an incoming web request.
2326
///
2427
/// This should be used sparingly in Rocket applications. In particular, it
@@ -51,6 +54,8 @@ pub(crate) struct RequestState<'r> {
5154
pub content_type: Storage<Option<ContentType>>,
5255
pub cache: Arc<Container![Send + Sync]>,
5356
pub host: Option<Host<'r>>,
57+
#[cfg(test)]
58+
pub route_path: Arc<Mutex<Vec<&'r Route>>>,
5459
}
5560

5661
impl Request<'_> {
@@ -76,6 +81,8 @@ impl RequestState<'_> {
7681
content_type: self.content_type.clone(),
7782
cache: self.cache.clone(),
7883
host: self.host.clone(),
84+
#[cfg(test)]
85+
route_path: self.route_path.clone(),
7986
}
8087
}
8188
}
@@ -105,6 +112,8 @@ impl<'r> Request<'r> {
105112
content_type: Storage::new(),
106113
cache: Arc::new(<Container![Send + Sync]>::new()),
107114
host: None,
115+
#[cfg(test)]
116+
route_path: Arc::new(Mutex::new(vec![])),
108117
}
109118
}
110119
}
@@ -968,7 +977,21 @@ impl<'r> Request<'r> {
968977
/// was `route`. Use during routing when attempting a given route.
969978
#[inline(always)]
970979
pub(crate) fn set_route(&self, route: Option<&'r Route>) {
971-
self.state.route.store(route, Ordering::Release)
980+
self.state.route.store(route, Ordering::Release);
981+
#[cfg(test)]
982+
if let Some(route) = route {
983+
self.state.route_path.lock().push(route);
984+
}
985+
}
986+
987+
/// Compute a value using the route path of this request
988+
///
989+
/// This doesn't simply return a refernce, since the reference is held behind a Arc<Mutex>.
990+
/// This method is only intended to be used internally, and is therefore NOT pub.
991+
#[inline(always)]
992+
#[cfg(test)]
993+
pub(crate) fn route_path<R>(&self, operation: impl FnOnce(&[&'r Route]) -> R) -> R {
994+
operation(self.state.route_path.lock().as_ref())
972995
}
973996

974997
/// Set `self`'s parameters given that the route used to reach this request

0 commit comments

Comments
 (0)