Skip to content

Commit a46af5d

Browse files
committed
migrate homepage, release-lists & search to axum
1 parent 7d6147d commit a46af5d

File tree

4 files changed

+309
-253
lines changed

4 files changed

+309
-253
lines changed

Diff for: src/web/error.rs

+12-33
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ pub enum Nope {
2222
OwnerNotFound,
2323
#[error("Requested crate does not have specified version")]
2424
VersionNotFound,
25-
#[error("Search yielded no results")]
26-
NoResults,
2725
#[error("Internal server error")]
2826
InternalServerError,
2927
}
@@ -37,8 +35,7 @@ impl From<Nope> for IronError {
3735
| Nope::BuildNotFound
3836
| Nope::CrateNotFound
3937
| Nope::OwnerNotFound
40-
| Nope::VersionNotFound
41-
| Nope::NoResults => status::NotFound,
38+
| Nope::VersionNotFound => status::NotFound,
4239
Nope::InternalServerError => status::InternalServerError,
4340
};
4441

@@ -96,29 +93,6 @@ impl Handler for Nope {
9693
.into_response(req)
9794
}
9895

99-
Nope::NoResults => {
100-
let mut params = req.url.as_ref().query_pairs();
101-
102-
if let Some((_, query)) = params.find(|(key, _)| key == "query") {
103-
// this used to be a search
104-
Search {
105-
title: format!("No crates found matching '{}'", query),
106-
search_query: Some(query.into_owned()),
107-
status: Status::NotFound,
108-
..Default::default()
109-
}
110-
.into_response(req)
111-
} else {
112-
// user did a search with no search terms
113-
Search {
114-
title: "No results given for empty search query".to_owned(),
115-
status: Status::NotFound,
116-
..Default::default()
117-
}
118-
.into_response(req)
119-
}
120-
}
121-
12296
Nope::InternalServerError => {
12397
// something went wrong, details should have been logged
12498
ErrorPage {
@@ -151,8 +125,8 @@ pub enum AxumNope {
151125
OwnerNotFound,
152126
#[error("Requested crate does not have specified version")]
153127
VersionNotFound,
154-
// #[error("Search yielded no results")]
155-
// NoResults,
128+
#[error("Search yielded no results")]
129+
NoResults,
156130
#[error("Internal server error")]
157131
InternalServerError,
158132
#[error("internal error")]
@@ -207,9 +181,15 @@ impl IntoResponse for AxumNope {
207181
}
208182
.into_response()
209183
}
210-
// AxumNope::NoResults => {
211-
// todo!("to be implemented when search-handler is migrated to axum")
212-
// }
184+
AxumNope::NoResults => {
185+
// user did a search with no search terms
186+
Search {
187+
title: "No results given for empty search query".to_owned(),
188+
status: StatusCode::NOT_FOUND,
189+
..Default::default()
190+
}
191+
.into_response()
192+
}
213193
AxumNope::InternalServerError => {
214194
// something went wrong, details should have been logged
215195
AxumErrorPage {
@@ -254,7 +234,6 @@ impl From<Nope> for AxumNope {
254234
Nope::CrateNotFound => AxumNope::CrateNotFound,
255235
Nope::OwnerNotFound => AxumNope::OwnerNotFound,
256236
Nope::VersionNotFound => AxumNope::VersionNotFound,
257-
Nope::NoResults => todo!(),
258237
Nope::InternalServerError => AxumNope::InternalServerError,
259238
}
260239
}

Diff for: src/web/mod.rs

+65-7
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
pub mod page;
44

55
use crate::utils::get_correct_docsrs_style_file;
6-
use crate::utils::report_error;
7-
use anyhow::anyhow;
6+
use crate::utils::{report_error, spawn_blocking};
7+
use anyhow::{anyhow, bail, Context as _};
88
use serde_json::Value;
99
use tracing::{info, instrument};
1010

@@ -92,7 +92,7 @@ mod source;
9292
mod statics;
9393
mod strangler;
9494

95-
use crate::{impl_axum_webpage, impl_webpage, Context};
95+
use crate::{db::Pool, impl_axum_webpage, impl_webpage, Context};
9696
use anyhow::Error;
9797
use axum::{
9898
extract::Extension,
@@ -123,6 +123,7 @@ use std::{borrow::Cow, net::SocketAddr, sync::Arc};
123123
use strangler::StranglerService;
124124
use tower::ServiceBuilder;
125125
use tower_http::trace::TraceLayer;
126+
use url::form_urlencoded;
126127

127128
/// Duration of static files for staticfile and DatabaseFileHandler (in seconds)
128129
const STATIC_FILE_CACHE_DURATION: u64 = 60 * 60 * 24 * 30 * 12; // 12 months
@@ -428,6 +429,26 @@ fn match_version(
428429
Err(Nope::VersionNotFound)
429430
}
430431

432+
// temporary wrapper around `match_version` for axum handlers.
433+
//
434+
// FIXME: this can go when we fully migrated to axum / async in web
435+
async fn match_version_axum(
436+
pool: &Pool,
437+
name: &str,
438+
input_version: Option<&str>,
439+
) -> Result<MatchVersion, Error> {
440+
spawn_blocking({
441+
let name = name.to_owned();
442+
let input_version = input_version.map(str::to_owned);
443+
let pool = pool.clone();
444+
move || {
445+
let mut conn = pool.get()?;
446+
Ok(match_version(&mut conn, &name, input_version.as_deref())?)
447+
}
448+
})
449+
.await
450+
}
451+
431452
#[instrument(skip_all)]
432453
pub(crate) fn build_axum_app(
433454
context: &dyn Context,
@@ -539,15 +560,29 @@ fn redirect(url: Url) -> Response {
539560
resp
540561
}
541562

542-
fn axum_redirect(url: &str) -> Result<impl IntoResponse, Error> {
543-
if !url.starts_with('/') || url.starts_with("//") {
544-
return Err(anyhow!("invalid redirect URL: {}", url));
563+
fn axum_redirect<U>(uri: U) -> Result<impl IntoResponse, Error>
564+
where
565+
U: TryInto<http::Uri>,
566+
<U as TryInto<http::Uri>>::Error: std::fmt::Debug,
567+
{
568+
let uri: http::Uri = uri
569+
.try_into()
570+
.map_err(|err| anyhow!("invalid URI: {:?}", err))?;
571+
572+
if let Some(path_and_query) = uri.path_and_query() {
573+
if path_and_query.as_str().starts_with("//") {
574+
bail!("protocol relative redirects are forbidden");
575+
}
576+
} else {
577+
// we always want a path to redirect to, even when it's just `/`
578+
bail!("missing path in URI");
545579
}
580+
546581
Ok((
547582
StatusCode::FOUND,
548583
[(
549584
http::header::LOCATION,
550-
http::HeaderValue::try_from(url).expect("invalid url for redirect"),
585+
http::HeaderValue::try_from(uri.to_string()).context("invalid uri for redirect")?,
551586
)],
552587
))
553588
}
@@ -605,6 +640,29 @@ where
605640
}
606641
}
607642

643+
/// Parse an URI into a http::Uri struct.
644+
/// When `queries` are given these are added to the URL,
645+
/// with empty `queries` the `?` will be omitted.
646+
pub(crate) fn axum_parse_uri_with_params<I, K, V>(uri: &str, queries: I) -> Result<http::Uri, Error>
647+
where
648+
I: IntoIterator,
649+
I::Item: Borrow<(K, V)>,
650+
K: AsRef<str>,
651+
V: AsRef<str>,
652+
{
653+
let mut queries = queries.into_iter().peekable();
654+
if queries.peek().is_some() {
655+
let query_params: String = form_urlencoded::Serializer::new(String::new())
656+
.extend_pairs(queries)
657+
.finish();
658+
format!("{uri}?{}", query_params)
659+
.parse::<http::Uri>()
660+
.context("error parsing URL")
661+
} else {
662+
uri.parse::<http::Uri>().context("error parsing URL")
663+
}
664+
}
665+
608666
/// MetaData used in header
609667
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
610668
pub(crate) struct MetaData {

0 commit comments

Comments
 (0)