Skip to content

Commit 116bb18

Browse files
committed
the fairing
1 parent 3734d28 commit 116bb18

3 files changed

Lines changed: 68 additions & 0 deletions

File tree

pointercrate-core-api/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod error;
22
pub mod etag;
33
pub mod localization;
44
pub mod maintenance;
5+
pub mod normalize_uri;
56
pub mod pagination;
67
pub mod preferences;
78
pub mod query;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use std::sync::OnceLock;
2+
3+
use rocket::{
4+
fairing::{Fairing, Info, Kind},
5+
Data, Orbit, Request, Rocket, Route,
6+
};
7+
8+
// heavily inspired by rocket's `rocket::fairing::AdHoc::uri_normalizer()` implementation
9+
// only difference is that this applies a trailing slash internally as opposed to omitting it
10+
// https://api.rocket.rs/master/src/rocket/fairing/ad_hoc.rs#315
11+
pub fn uri_normalizer() -> impl Fairing {
12+
#[derive(Default)]
13+
struct Normalizer {
14+
routes: OnceLock<Vec<Route>>,
15+
}
16+
17+
impl Normalizer {
18+
fn routes(&self, rocket: &Rocket<Orbit>) -> &[Route] {
19+
// gather all defined routes which have a trailing slash
20+
self.routes.get_or_init(|| {
21+
rocket
22+
.routes()
23+
.filter(|r| r.uri.has_trailing_slash() || r.uri.path() == "/")
24+
.cloned()
25+
.collect()
26+
})
27+
}
28+
}
29+
30+
#[rocket::async_trait]
31+
impl Fairing for Normalizer {
32+
fn info(&self) -> Info {
33+
Info {
34+
name: "URI Normalizer",
35+
kind: Kind::Liftoff | Kind::Request,
36+
}
37+
}
38+
39+
async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
40+
let _ = self.routes(rocket);
41+
}
42+
43+
async fn on_request(&self, request: &mut Request<'_>, _: &mut Data<'_>) {
44+
if request.uri().has_trailing_slash() {
45+
return;
46+
}
47+
48+
if let Some(normalized) = request.uri().map_path(|p| format!("{}/", p)) {
49+
// check if the normalized uri (the request uri with a trailing slash) matches one of our defined routes
50+
if self.routes(request.rocket()).iter().any(|r| {
51+
// we need to leverage rocket's route matching otherwise this will suck
52+
let mut normalized_req = request.clone();
53+
normalized_req.set_uri(normalized.clone());
54+
r.matches(&normalized_req)
55+
}) {
56+
// the request doesn't have a trailing slash AND it's trying to reach one of our defined routes
57+
// so just point it to our defined route
58+
request.set_uri(normalized);
59+
}
60+
}
61+
}
62+
}
63+
64+
Normalizer::default()
65+
}

pointercrate-example/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use maud::html;
22
use pointercrate_core::localization::LocalesLoader;
33
use pointercrate_core::pool::PointercratePool;
44
use pointercrate_core::{error::CoreError, localization::tr};
5+
use pointercrate_core_api::normalize_uri::uri_normalizer;
56
use pointercrate_core_api::{error::ErrorResponder, maintenance::MaintenanceFairing, preferences::PreferenceManager};
67
use pointercrate_core_macros::localized_catcher;
78
use pointercrate_core_pages::{
@@ -178,6 +179,7 @@ async fn rocket() -> _ {
178179
// static files.
179180

180181
rocket
182+
.attach(uri_normalizer())
181183
.mount("/static/core", FileServer::new("pointercrate-core-pages/static"))
182184
.mount("/static/demonlist", FileServer::new("pointercrate-demonlist-pages/static"))
183185
.mount("/static/user", FileServer::new("pointercrate-user-pages/static"))

0 commit comments

Comments
 (0)