diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e88baf106..7855e6d55 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,3 @@ [toolchain] channel = "1.88.0" +components = ["rustfmt", "clippy"] diff --git a/src/test/mod.rs b/src/test/mod.rs index 45b7e017e..8b1a62e7a 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -289,7 +289,9 @@ impl AxumRouterTestExt for axum::Router { // } if redirect_target != expected_target { - anyhow::bail!("got redirect to {redirect_target}"); + anyhow::bail!( + "got redirect to `{redirect_target}`, expected redirect to `{expected_target}`", + ); } Ok(response) diff --git a/src/web/releases.rs b/src/web/releases.rs index 2696f26c5..9ba6856c5 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -50,6 +50,7 @@ pub struct Release { pub(crate) build_time: Option>, pub(crate) stars: i32, pub(crate) has_unyanked_releases: Option, + pub(crate) href: Option<&'static str>, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -124,6 +125,7 @@ pub(crate) async fn get_releases( build_time: row.get(5), stars: row.get::, _>(6).unwrap_or(0), has_unyanked_releases: None, + href: None, }) .try_collect() .await?) @@ -142,6 +144,20 @@ struct SearchResult { pub next_page: Option, } +fn rust_lib_release(name: &str, description: &str, href: &'static str) -> ReleaseStatus { + ReleaseStatus::Available(Release { + name: name.to_string(), + version: String::new(), + description: Some(description.to_string()), + build_time: None, + target_name: None, + rustdoc_status: false, + stars: 0, + has_unyanked_releases: None, + href: Some(href), + }) +} + /// Get the search results for a crate search query /// /// This delegates to the crates.io search API. @@ -149,6 +165,7 @@ async fn get_search_results( conn: &mut sqlx::PgConnection, registry: &RegistryApi, query_params: &str, + query: &str, ) -> Result { let crate::registry_api::Search { crates, meta } = registry.search(query_params).await?; @@ -206,28 +223,38 @@ async fn get_search_results( rustdoc_status: row.rustdoc_status.unwrap_or(false), stars: row.stars.unwrap_or(0), has_unyanked_releases: row.has_unyanked_releases, + href: None, }, ) }) .try_collect() .await?; + // start with the original names from crates.io to keep the original ranking, + // extend with the release/build information from docs.rs + // Crates that are not on docs.rs yet will not be returned. + let mut results = Vec::new(); + if let Some(super::rustdoc::OfficialCrateDescription { + name, + href, + description, + }) = super::rustdoc::DOC_RUST_LANG_ORG_REDIRECTS.get(query) + { + results.push(rust_lib_release(name, description, href)) + } + let names: Vec = Arc::into_inner(names).expect("Arc still borrowed in `get_search_results`"); + results.extend(names.into_iter().map(|name| { + if let Some(release) = crates.remove(&name) { + ReleaseStatus::Available(release) + } else { + ReleaseStatus::NotAvailable(name) + } + })); + Ok(SearchResult { - // start with the original names from crates.io to keep the original ranking, - // extend with the release/build information from docs.rs - // Crates that are not on docs.rs yet will not be returned. - results: names - .into_iter() - .map(|name| { - if let Some(release) = crates.remove(&name) { - ReleaseStatus::Available(release) - } else { - ReleaseStatus::NotAvailable(name) - } - }) - .collect(), + results, prev_page: meta.prev_page, next_page: meta.next_page, }) @@ -589,7 +616,7 @@ pub(crate) async fn search_handler( } } - get_search_results(&mut conn, ®istry, query_params).await? + get_search_results(&mut conn, ®istry, query_params, "").await? } else if !query.is_empty() { let query_params: String = form_urlencoded::Serializer::new(String::new()) .append_pair("q", &query) @@ -597,7 +624,7 @@ pub(crate) async fn search_handler( .append_pair("per_page", &RELEASES_IN_RELEASES.to_string()) .finish(); - get_search_results(&mut conn, ®istry, &query_params).await? + get_search_results(&mut conn, ®istry, &query_params, &query).await? } else { return Err(AxumNope::NoResults); }; @@ -2231,4 +2258,55 @@ mod tests { Ok(()) }); } + + #[test] + fn test_search_std() { + async_wrapper(|env| async move { + let web = env.web_app().await; + + async fn inner(web: &axum::Router, krate: &str) -> Result<(), anyhow::Error> { + let full = kuchikiki::parse_html().one( + web.get(&format!("/releases/search?query={krate}")) + .await? + .text() + .await?, + ); + let items = full + .select("ul a.release") + .expect("missing list items") + .collect::>(); + + // empty because expand_rebuild_queue is not set + let item_element = items.first().unwrap(); + let item = item_element.as_node(); + assert_eq!( + item.select(".name") + .unwrap() + .next() + .unwrap() + .text_contents(), + "std" + ); + assert_eq!( + item.select(".description") + .unwrap() + .next() + .unwrap() + .text_contents(), + "Rust standard library", + ); + assert_eq!( + item_element.attributes.borrow().get("href").unwrap(), + "https://doc.rust-lang.org/stable/std/" + ); + + Ok(()) + } + + inner(&web, "std").await?; + inner(&web, "libstd").await?; + + Ok(()) + }); + } } diff --git a/src/web/rustdoc.rs b/src/web/rustdoc.rs index 46316a4f7..dd2f7e383 100644 --- a/src/web/rustdoc.rs +++ b/src/web/rustdoc.rs @@ -43,18 +43,129 @@ use tracing::{Instrument, debug, error, info_span, instrument, trace}; use super::extractors::PathFileExtension; -static DOC_RUST_LANG_ORG_REDIRECTS: Lazy> = Lazy::new(|| { - HashMap::from([ - ("alloc", "stable/alloc"), - ("core", "stable/core"), - ("proc_macro", "stable/proc_macro"), - ("proc-macro", "stable/proc_macro"), - ("std", "stable/std"), - ("test", "stable/test"), - ("rustc", "nightly/nightly-rustc"), - ("rustdoc", "nightly/nightly-rustc/rustdoc"), - ]) -}); +pub(crate) struct OfficialCrateDescription { + pub(crate) name: &'static str, + pub(crate) href: &'static str, + pub(crate) description: &'static str, +} + +pub(crate) static DOC_RUST_LANG_ORG_REDIRECTS: Lazy> = + Lazy::new(|| { + HashMap::from([ + ( + "alloc", + OfficialCrateDescription { + name: "alloc", + href: "https://doc.rust-lang.org/stable/alloc/", + description: "Rust alloc library", + }, + ), + ( + "liballoc", + OfficialCrateDescription { + name: "alloc", + href: "https://doc.rust-lang.org/stable/alloc/", + description: "Rust alloc library", + }, + ), + ( + "core", + OfficialCrateDescription { + name: "core", + href: "https://doc.rust-lang.org/stable/core/", + description: "Rust core library", + }, + ), + ( + "libcore", + OfficialCrateDescription { + name: "core", + href: "https://doc.rust-lang.org/stable/core/", + description: "Rust core library", + }, + ), + ( + "proc_macro", + OfficialCrateDescription { + name: "proc_macro", + href: "https://doc.rust-lang.org/stable/proc_macro/", + description: "Rust proc_macro library", + }, + ), + ( + "libproc_macro", + OfficialCrateDescription { + name: "proc_macro", + href: "https://doc.rust-lang.org/stable/proc_macro/", + description: "Rust proc_macro library", + }, + ), + ( + "proc-macro", + OfficialCrateDescription { + name: "proc_macro", + href: "https://doc.rust-lang.org/stable/proc_macro/", + description: "Rust proc_macro library", + }, + ), + ( + "libproc-macro", + OfficialCrateDescription { + name: "proc_macro", + href: "https://doc.rust-lang.org/stable/proc_macro/", + description: "Rust proc_macro library", + }, + ), + ( + "std", + OfficialCrateDescription { + name: "std", + href: "https://doc.rust-lang.org/stable/std/", + description: "Rust standard library", + }, + ), + ( + "libstd", + OfficialCrateDescription { + name: "std", + href: "https://doc.rust-lang.org/stable/std/", + description: "Rust standard library", + }, + ), + ( + "test", + OfficialCrateDescription { + name: "test", + href: "https://doc.rust-lang.org/stable/test/", + description: "Rust test library", + }, + ), + ( + "libtest", + OfficialCrateDescription { + name: "test", + href: "https://doc.rust-lang.org/stable/test/", + description: "Rust test library", + }, + ), + ( + "rustc", + OfficialCrateDescription { + name: "rustc", + href: "https://doc.rust-lang.org/nightly/nightly-rustc/", + description: "rustc API", + }, + ), + ( + "rustdoc", + OfficialCrateDescription { + name: "rustdoc", + href: "https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc/", + description: "rustdoc API", + }, + ), + ]) + }); #[derive(Debug, Clone, Deserialize)] pub(crate) struct RustdocRedirectorParams { @@ -151,10 +262,10 @@ pub(crate) async fn rustdoc_redirector_handler( None => (params.name.to_string(), None), }; - if let Some(inner_path) = DOC_RUST_LANG_ORG_REDIRECTS.get(crate_name.as_str()) { + if let Some(description) = DOC_RUST_LANG_ORG_REDIRECTS.get(crate_name.as_str()) { return Ok(redirect_to_doc( &query_pairs, - format!("https://doc.rust-lang.org/{inner_path}/"), + description.href.to_string(), CachePolicy::ForeverInCdnAndStaleInBrowser, path_in_crate.as_deref(), )? diff --git a/templates/releases/releases.html b/templates/releases/releases.html index 02f51f52f..49d32a43e 100644 --- a/templates/releases/releases.html +++ b/templates/releases/releases.html @@ -47,23 +47,26 @@ {%- else -%} {%- set release_version = release.version -%} {%- endif -%} - {% set link %} - {%- if release.rustdoc_status -%} + {%- set link -%} + {%- if let Some(href) = release.href -%} + {% set link = href.to_string() -%} + {%- elif release.rustdoc_status -%} {% set link = "/{}/{}/{}/"|format(release.name, release_version, release.target_name.as_deref().unwrap_or_default()) -%} {%- else -%} {% set link = "/crate/{}/{}"|format(release.name, release_version) -%} {%- endif -%} - - {#- -#} {%- endmatch -%} {%- endfor -%} - + {#- -#}