Skip to content

Commit 36b31f8

Browse files
Add search alias for Rust official crates
1 parent db5df21 commit 36b31f8

File tree

4 files changed

+238
-44
lines changed

4 files changed

+238
-44
lines changed

src/test/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,9 @@ impl AxumRouterTestExt for axum::Router {
289289
// }
290290

291291
if redirect_target != expected_target {
292-
anyhow::bail!("got redirect to {redirect_target}");
292+
anyhow::bail!(
293+
"got redirect to `{redirect_target}`, expected redirect to `{expected_target}`",
294+
);
293295
}
294296

295297
Ok(response)

src/web/releases.rs

Lines changed: 93 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub struct Release {
5050
pub(crate) build_time: Option<DateTime<Utc>>,
5151
pub(crate) stars: i32,
5252
pub(crate) has_unyanked_releases: Option<bool>,
53+
pub(crate) href: Option<&'static str>,
5354
}
5455

5556
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -124,6 +125,7 @@ pub(crate) async fn get_releases(
124125
build_time: row.get(5),
125126
stars: row.get::<Option<i32>, _>(6).unwrap_or(0),
126127
has_unyanked_releases: None,
128+
href: None,
127129
})
128130
.try_collect()
129131
.await?)
@@ -142,13 +144,28 @@ struct SearchResult {
142144
pub next_page: Option<String>,
143145
}
144146

147+
fn rust_lib_release(name: &str, description: &str, href: &'static str) -> ReleaseStatus {
148+
ReleaseStatus::Available(Release {
149+
name: name.to_string(),
150+
version: String::new(),
151+
description: Some(description.to_string()),
152+
build_time: None,
153+
target_name: None,
154+
rustdoc_status: false,
155+
stars: 0,
156+
has_unyanked_releases: None,
157+
href: Some(href),
158+
})
159+
}
160+
145161
/// Get the search results for a crate search query
146162
///
147163
/// This delegates to the crates.io search API.
148164
async fn get_search_results(
149165
conn: &mut sqlx::PgConnection,
150166
registry: &RegistryApi,
151167
query_params: &str,
168+
query: &str,
152169
) -> Result<SearchResult, anyhow::Error> {
153170
let crate::registry_api::Search { crates, meta } = registry.search(query_params).await?;
154171

@@ -206,28 +223,38 @@ async fn get_search_results(
206223
rustdoc_status: row.rustdoc_status.unwrap_or(false),
207224
stars: row.stars.unwrap_or(0),
208225
has_unyanked_releases: row.has_unyanked_releases,
226+
href: None,
209227
},
210228
)
211229
})
212230
.try_collect()
213231
.await?;
214232

233+
// start with the original names from crates.io to keep the original ranking,
234+
// extend with the release/build information from docs.rs
235+
// Crates that are not on docs.rs yet will not be returned.
236+
let mut results = Vec::new();
237+
if let Some(super::rustdoc::OfficialCrateDescription {
238+
name,
239+
href,
240+
description,
241+
}) = super::rustdoc::DOC_RUST_LANG_ORG_REDIRECTS.get(query)
242+
{
243+
results.push(rust_lib_release(name, description, href))
244+
}
245+
215246
let names: Vec<String> =
216247
Arc::into_inner(names).expect("Arc still borrowed in `get_search_results`");
248+
results.extend(names.into_iter().map(|name| {
249+
if let Some(release) = crates.remove(&name) {
250+
ReleaseStatus::Available(release)
251+
} else {
252+
ReleaseStatus::NotAvailable(name)
253+
}
254+
}));
255+
217256
Ok(SearchResult {
218-
// start with the original names from crates.io to keep the original ranking,
219-
// extend with the release/build information from docs.rs
220-
// Crates that are not on docs.rs yet will not be returned.
221-
results: names
222-
.into_iter()
223-
.map(|name| {
224-
if let Some(release) = crates.remove(&name) {
225-
ReleaseStatus::Available(release)
226-
} else {
227-
ReleaseStatus::NotAvailable(name)
228-
}
229-
})
230-
.collect(),
257+
results,
231258
prev_page: meta.prev_page,
232259
next_page: meta.next_page,
233260
})
@@ -589,15 +616,15 @@ pub(crate) async fn search_handler(
589616
}
590617
}
591618

592-
get_search_results(&mut conn, &registry, query_params).await?
619+
get_search_results(&mut conn, &registry, query_params, "").await?
593620
} else if !query.is_empty() {
594621
let query_params: String = form_urlencoded::Serializer::new(String::new())
595622
.append_pair("q", &query)
596623
.append_pair("sort", &sort_by)
597624
.append_pair("per_page", &RELEASES_IN_RELEASES.to_string())
598625
.finish();
599626

600-
get_search_results(&mut conn, &registry, &query_params).await?
627+
get_search_results(&mut conn, &registry, &query_params, &query).await?
601628
} else {
602629
return Err(AxumNope::NoResults);
603630
};
@@ -2231,4 +2258,55 @@ mod tests {
22312258
Ok(())
22322259
});
22332260
}
2261+
2262+
#[test]
2263+
fn test_search_std() {
2264+
async_wrapper(|env| async move {
2265+
let web = env.web_app().await;
2266+
2267+
async fn inner(web: &axum::Router, krate: &str) -> Result<(), anyhow::Error> {
2268+
let full = kuchikiki::parse_html().one(
2269+
web.get(&format!("/releases/search?query={krate}"))
2270+
.await?
2271+
.text()
2272+
.await?,
2273+
);
2274+
let items = full
2275+
.select("ul a.release")
2276+
.expect("missing list items")
2277+
.collect::<Vec<_>>();
2278+
2279+
// empty because expand_rebuild_queue is not set
2280+
let item_element = items.first().unwrap();
2281+
let item = item_element.as_node();
2282+
assert_eq!(
2283+
item.select(".name")
2284+
.unwrap()
2285+
.next()
2286+
.unwrap()
2287+
.text_contents(),
2288+
"std"
2289+
);
2290+
assert_eq!(
2291+
item.select(".description")
2292+
.unwrap()
2293+
.next()
2294+
.unwrap()
2295+
.text_contents(),
2296+
"Rust standard library",
2297+
);
2298+
assert_eq!(
2299+
item_element.attributes.borrow().get("href").unwrap(),
2300+
"https://doc.rust-lang.org/stable/std/"
2301+
);
2302+
2303+
Ok(())
2304+
}
2305+
2306+
inner(&web, "std").await?;
2307+
inner(&web, "libstd").await?;
2308+
2309+
Ok(())
2310+
});
2311+
}
22342312
}

src/web/rustdoc.rs

Lines changed: 125 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,129 @@ use tracing::{Instrument, debug, error, info_span, instrument, trace};
4343

4444
use super::extractors::PathFileExtension;
4545

46-
static DOC_RUST_LANG_ORG_REDIRECTS: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
47-
HashMap::from([
48-
("alloc", "stable/alloc"),
49-
("core", "stable/core"),
50-
("proc_macro", "stable/proc_macro"),
51-
("proc-macro", "stable/proc_macro"),
52-
("std", "stable/std"),
53-
("test", "stable/test"),
54-
("rustc", "nightly/nightly-rustc"),
55-
("rustdoc", "nightly/nightly-rustc/rustdoc"),
56-
])
57-
});
46+
pub(crate) struct OfficialCrateDescription {
47+
pub(crate) name: &'static str,
48+
pub(crate) href: &'static str,
49+
pub(crate) description: &'static str,
50+
}
51+
52+
pub(crate) static DOC_RUST_LANG_ORG_REDIRECTS: Lazy<HashMap<&str, OfficialCrateDescription>> =
53+
Lazy::new(|| {
54+
HashMap::from([
55+
(
56+
"alloc",
57+
OfficialCrateDescription {
58+
name: "alloc",
59+
href: "https://doc.rust-lang.org/stable/alloc/",
60+
description: "Rust alloc library",
61+
},
62+
),
63+
(
64+
"liballoc",
65+
OfficialCrateDescription {
66+
name: "alloc",
67+
href: "https://doc.rust-lang.org/stable/alloc/",
68+
description: "Rust alloc library",
69+
},
70+
),
71+
(
72+
"core",
73+
OfficialCrateDescription {
74+
name: "core",
75+
href: "https://doc.rust-lang.org/stable/core/",
76+
description: "Rust core library",
77+
},
78+
),
79+
(
80+
"libcore",
81+
OfficialCrateDescription {
82+
name: "core",
83+
href: "https://doc.rust-lang.org/stable/core/",
84+
description: "Rust core library",
85+
},
86+
),
87+
(
88+
"proc_macro",
89+
OfficialCrateDescription {
90+
name: "proc_macro",
91+
href: "https://doc.rust-lang.org/stable/proc_macro/",
92+
description: "Rust proc_macro library",
93+
},
94+
),
95+
(
96+
"libproc_macro",
97+
OfficialCrateDescription {
98+
name: "proc_macro",
99+
href: "https://doc.rust-lang.org/stable/proc_macro/",
100+
description: "Rust proc_macro library",
101+
},
102+
),
103+
(
104+
"proc-macro",
105+
OfficialCrateDescription {
106+
name: "proc_macro",
107+
href: "https://doc.rust-lang.org/stable/proc_macro/",
108+
description: "Rust proc_macro library",
109+
},
110+
),
111+
(
112+
"libproc-macro",
113+
OfficialCrateDescription {
114+
name: "proc_macro",
115+
href: "https://doc.rust-lang.org/stable/proc_macro/",
116+
description: "Rust proc_macro library",
117+
},
118+
),
119+
(
120+
"std",
121+
OfficialCrateDescription {
122+
name: "std",
123+
href: "https://doc.rust-lang.org/stable/std/",
124+
description: "Rust standard library",
125+
},
126+
),
127+
(
128+
"libstd",
129+
OfficialCrateDescription {
130+
name: "std",
131+
href: "https://doc.rust-lang.org/stable/std/",
132+
description: "Rust standard library",
133+
},
134+
),
135+
(
136+
"test",
137+
OfficialCrateDescription {
138+
name: "test",
139+
href: "https://doc.rust-lang.org/stable/test/",
140+
description: "Rust test library",
141+
},
142+
),
143+
(
144+
"libtest",
145+
OfficialCrateDescription {
146+
name: "test",
147+
href: "https://doc.rust-lang.org/stable/test/",
148+
description: "Rust test library",
149+
},
150+
),
151+
(
152+
"rustc",
153+
OfficialCrateDescription {
154+
name: "rustc",
155+
href: "https://doc.rust-lang.org/nightly/nightly-rustc/",
156+
description: "rustc API",
157+
},
158+
),
159+
(
160+
"rustdoc",
161+
OfficialCrateDescription {
162+
name: "rustdoc",
163+
href: "https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc/",
164+
description: "rustdoc API",
165+
},
166+
),
167+
])
168+
});
58169

59170
#[derive(Debug, Clone, Deserialize)]
60171
pub(crate) struct RustdocRedirectorParams {
@@ -151,10 +262,10 @@ pub(crate) async fn rustdoc_redirector_handler(
151262
None => (params.name.to_string(), None),
152263
};
153264

154-
if let Some(inner_path) = DOC_RUST_LANG_ORG_REDIRECTS.get(crate_name.as_str()) {
265+
if let Some(description) = DOC_RUST_LANG_ORG_REDIRECTS.get(crate_name.as_str()) {
155266
return Ok(redirect_to_doc(
156267
&query_pairs,
157-
format!("https://doc.rust-lang.org/{inner_path}/"),
268+
description.href.to_string(),
158269
CachePolicy::ForeverInCdnAndStaleInBrowser,
159270
path_in_crate.as_deref(),
160271
)?

0 commit comments

Comments
 (0)