Skip to content

Commit f7f4fb4

Browse files
committed
Shell out to crates.io for search
This is a much smaller maintenance burden and fixes a lot of our bugs. TODO: fix tests
1 parent 33ddc64 commit f7f4fb4

File tree

2 files changed

+78
-71
lines changed

2 files changed

+78
-71
lines changed

src/utils/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,9 @@ mod queue;
2222
mod queue_builder;
2323
mod rustc_version;
2424
pub(crate) mod sized_buffer;
25+
26+
pub(crate) const APP_USER_AGENT: &str = concat!(
27+
env!("CARGO_PKG_NAME"),
28+
" ",
29+
include_str!(concat!(env!("OUT_DIR"), "/git_version"))
30+
);

src/web/releases.rs

Lines changed: 72 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ use iron::{
1414
modifiers::Redirect,
1515
status, IronResult, Request, Response, Url,
1616
};
17+
use log::debug;
1718
use postgres::Client;
1819
use router::Router;
19-
use serde::Serialize;
20+
use serde::{Deserialize, Serialize};
2021

2122
/// Number of release in home page
2223
const RELEASES_IN_HOME: i64 = 15;
@@ -165,84 +166,84 @@ fn get_releases_by_owner(
165166

166167
/// Get the search results for a crate search query
167168
///
168-
/// Retrieves crates which names have a levenshtein distance of less than or equal to 3,
169-
/// crates who fit into or otherwise are made up of the query or crates whose descriptions
170-
/// match the search query.
171-
///
172-
/// * `query`: The query string, unfiltered
173-
/// * `page`: The page of results to show (1-indexed)
174-
/// * `limit`: The number of results to return
175-
///
176-
/// Returns 0 and an empty Vec when no results are found or if a database error occurs
177-
///
169+
/// This delegates to the crates.io search API.
178170
fn get_search_results(
179171
conn: &mut Client,
180-
mut query: &str,
172+
query: &str,
181173
page: i64,
182174
limit: i64,
183-
) -> Result<(i64, Vec<Release>), failure::Error> {
184-
query = query.trim();
185-
if query.is_empty() {
186-
return Ok((0, Vec::new()));
175+
) -> Result<(u64, Vec<Release>), failure::Error> {
176+
#[derive(Deserialize)]
177+
struct CratesIoReleases {
178+
crates: Vec<CratesIoRelease>,
179+
meta: CratesIoMeta,
180+
}
181+
#[derive(Deserialize, Debug)]
182+
struct CratesIoRelease {
183+
name: String,
184+
max_version: String,
185+
description: Option<String>,
186+
updated_at: DateTime<Utc>,
187+
}
188+
#[derive(Deserialize)]
189+
struct CratesIoMeta {
190+
total: u64,
187191
}
188-
let offset = (page - 1) * limit;
189192

190-
let statement = "
191-
SELECT
192-
crates.name AS name,
193-
releases.version AS version,
194-
releases.description AS description,
195-
releases.target_name AS target_name,
196-
releases.release_time AS release_time,
197-
releases.rustdoc_status AS rustdoc_status,
198-
repositories.stars AS stars,
199-
COUNT(*) OVER() as total
200-
FROM crates
201-
INNER JOIN (
202-
SELECT releases.id, releases.crate_id
203-
FROM (
204-
SELECT
205-
releases.id,
206-
releases.crate_id,
207-
RANK() OVER (PARTITION BY crate_id ORDER BY release_time DESC) as rank
208-
FROM releases
209-
WHERE releases.rustdoc_status AND NOT releases.yanked
210-
) AS releases
211-
WHERE releases.rank = 1
212-
) AS latest_release ON latest_release.crate_id = crates.id
213-
INNER JOIN releases ON latest_release.id = releases.id
214-
LEFT JOIN repositories ON releases.repository_id = repositories.id
215-
WHERE
216-
((char_length($1)::float - levenshtein(crates.name, $1)::float) / char_length($1)::float) >= 0.65
217-
OR crates.name ILIKE CONCAT('%', $1, '%')
218-
GROUP BY crates.id, releases.id, repositories.stars
219-
ORDER BY
220-
levenshtein(crates.name, $1) ASC,
221-
crates.name ILIKE CONCAT('%', $1, '%'),
222-
releases.downloads DESC
223-
LIMIT $2 OFFSET $3";
224-
225-
let rows = conn.query(statement, &[&query, &limit, &offset])?;
226-
227-
// Each row contains the total number of possible/valid results, just get it once
228-
let total_results = rows
229-
.get(0)
230-
.map(|row| row.get::<_, i64>("total"))
231-
.unwrap_or_default();
232-
let packages: Vec<Release> = rows
193+
use crate::utils::APP_USER_AGENT;
194+
use once_cell::sync::Lazy;
195+
use reqwest::blocking::Client as HttpClient;
196+
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, USER_AGENT};
197+
198+
static HTTP_CLIENT: Lazy<HttpClient> = Lazy::new(|| {
199+
let mut headers = HeaderMap::new();
200+
headers.insert(USER_AGENT, HeaderValue::from_static(APP_USER_AGENT));
201+
headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
202+
HttpClient::builder()
203+
.default_headers(headers)
204+
.build()
205+
.unwrap()
206+
});
207+
208+
let url = format!(
209+
"https://crates.io/api/v1/crates?page={page}&per_page={limit}&q={query}",
210+
page = page,
211+
limit = limit,
212+
query = query
213+
);
214+
debug!("fetching search results from {}", url);
215+
let releases: CratesIoReleases = HTTP_CLIENT.get(&url).send()?.json()?;
216+
let query = conn.prepare(
217+
"SELECT github_repos.stars, releases.target_name, releases.rustdoc_status
218+
FROM crates INNER JOIN releases ON crates.id = releases.crate_id
219+
LEFT JOIN github_repos ON releases.github_repo = github_repos.id
220+
WHERE crates.name = $1 AND releases.version = $2",
221+
)?;
222+
let crates = releases
223+
.crates
233224
.into_iter()
234-
.map(|row| Release {
235-
name: row.get("name"),
236-
version: row.get("version"),
237-
description: row.get("description"),
238-
target_name: row.get("target_name"),
239-
release_time: row.get("release_time"),
240-
rustdoc_status: row.get("rustdoc_status"),
241-
stars: row.get::<_, Option<i32>>("stars").unwrap_or(0),
225+
.flat_map(|krate| {
226+
let rows = match conn.query(&query, &[&krate.name, &krate.max_version]) {
227+
Err(e) => return Some(Err(e)),
228+
Ok(rows) => rows,
229+
};
230+
debug!("looking up results for {:?}", krate);
231+
// crates.io could have a release that hasn't yet been added to the database.
232+
// If so, just skip it.
233+
let row = rows.get(0)?;
234+
let stars: Option<_> = row.get("stars");
235+
Some(Result::<_, postgres::Error>::Ok(Release {
236+
name: krate.name,
237+
version: krate.max_version,
238+
description: krate.description,
239+
release_time: krate.updated_at,
240+
target_name: row.get("target_name"),
241+
rustdoc_status: row.get("rustdoc_status"),
242+
stars: stars.unwrap_or(0),
243+
}))
242244
})
243-
.collect();
244-
245-
Ok((total_results, packages))
245+
.collect::<Result<_, _>>()?;
246+
Ok((releases.meta.total, crates))
246247
}
247248

248249
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]

0 commit comments

Comments
 (0)