@@ -14,9 +14,10 @@ use iron::{
14
14
modifiers:: Redirect ,
15
15
status, IronResult , Request , Response , Url ,
16
16
} ;
17
+ use log:: debug;
17
18
use postgres:: Client ;
18
19
use router:: Router ;
19
- use serde:: Serialize ;
20
+ use serde:: { Deserialize , Serialize } ;
20
21
21
22
/// Number of release in home page
22
23
const RELEASES_IN_HOME : i64 = 15 ;
@@ -165,84 +166,84 @@ fn get_releases_by_owner(
165
166
166
167
/// Get the search results for a crate search query
167
168
///
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.
178
170
fn get_search_results (
179
171
conn : & mut Client ,
180
- mut query : & str ,
172
+ query : & str ,
181
173
page : i64 ,
182
174
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 ,
187
191
}
188
- let offset = ( page - 1 ) * limit;
189
192
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
233
224
. 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
+ } ) )
242
244
} )
243
- . collect ( ) ;
244
-
245
- Ok ( ( total_results, packages) )
245
+ . collect :: < Result < _ , _ > > ( ) ?;
246
+ Ok ( ( releases. meta . total , crates) )
246
247
}
247
248
248
249
#[ derive( Debug , Clone , PartialEq , Eq , Serialize ) ]
0 commit comments