3
3
use crate :: {
4
4
build_queue:: { QueuedCrate , REBUILD_PRIORITY } ,
5
5
cdn, impl_axum_webpage,
6
- utils:: { report_error, retry_async } ,
6
+ utils:: report_error,
7
7
web:: {
8
8
axum_parse_uri_with_params, axum_redirect, encode_url_path,
9
9
error:: { AxumNope , AxumResult } ,
@@ -12,9 +12,9 @@ use crate::{
12
12
page:: templates:: { filters, RenderRegular , RenderSolid } ,
13
13
ReqVersion ,
14
14
} ,
15
- AsyncBuildQueue , Config , InstanceMetrics ,
15
+ AsyncBuildQueue , Config , InstanceMetrics , RegistryApi ,
16
16
} ;
17
- use anyhow:: { anyhow, bail , Context as _, Result } ;
17
+ use anyhow:: { anyhow, Context as _, Result } ;
18
18
use axum:: {
19
19
extract:: { Extension , Query } ,
20
20
response:: { IntoResponse , Response as AxumResponse } ,
@@ -23,14 +23,13 @@ use base64::{engine::general_purpose::STANDARD as b64, Engine};
23
23
use chrono:: { DateTime , Utc } ;
24
24
use futures_util:: stream:: TryStreamExt ;
25
25
use itertools:: Itertools ;
26
- use once_cell:: sync:: Lazy ;
27
26
use rinja:: Template ;
28
27
use serde:: { Deserialize , Serialize } ;
29
28
use sqlx:: Row ;
30
29
use std:: collections:: { BTreeMap , HashMap , HashSet } ;
31
30
use std:: str;
32
31
use std:: sync:: Arc ;
33
- use tracing:: { debug , warn} ;
32
+ use tracing:: warn;
34
33
use url:: form_urlencoded;
35
34
36
35
use super :: cache:: CachePolicy ;
@@ -143,85 +142,14 @@ struct SearchResult {
143
142
/// This delegates to the crates.io search API.
144
143
async fn get_search_results (
145
144
conn : & mut sqlx:: PgConnection ,
146
- config : & Config ,
147
- query_params : & str ,
145
+ registry : & RegistryApi ,
146
+ query_params : Option < & str > ,
148
147
) -> Result < SearchResult , anyhow:: Error > {
149
- #[ derive( Deserialize ) ]
150
- struct CratesIoError {
151
- detail : String ,
152
- }
153
- #[ derive( Deserialize ) ]
154
- struct CratesIoSearchResult {
155
- crates : Option < Vec < CratesIoCrate > > ,
156
- meta : Option < CratesIoMeta > ,
157
- errors : Option < Vec < CratesIoError > > ,
158
- }
159
- #[ derive( Deserialize , Debug ) ]
160
- struct CratesIoCrate {
161
- name : String ,
162
- }
163
- #[ derive( Deserialize , Debug ) ]
164
- struct CratesIoMeta {
165
- next_page : Option < String > ,
166
- prev_page : Option < String > ,
167
- }
168
-
169
- use crate :: utils:: APP_USER_AGENT ;
170
- use reqwest:: header:: { HeaderMap , HeaderValue , ACCEPT , USER_AGENT } ;
171
- use reqwest:: Client as HttpClient ;
172
-
173
- static HTTP_CLIENT : Lazy < HttpClient > = Lazy :: new ( || {
174
- let mut headers = HeaderMap :: new ( ) ;
175
- headers. insert ( USER_AGENT , HeaderValue :: from_static ( APP_USER_AGENT ) ) ;
176
- headers. insert ( ACCEPT , HeaderValue :: from_static ( "application/json" ) ) ;
177
- HttpClient :: builder ( )
178
- . default_headers ( headers)
179
- . build ( )
180
- . unwrap ( )
181
- } ) ;
182
-
183
- let url = config
184
- . registry_api_host
185
- . join ( & format ! ( "api/v1/crates{query_params}" ) ) ?;
186
- debug ! ( "fetching search results from {}" , url) ;
187
-
188
- // extract the query from the query args.
189
- // This is easier because the query might have been encoded in the bash64-encoded
190
- // paginate parameter.
191
- let executed_query = url. query_pairs ( ) . find_map ( |( key, value) | {
192
- if key == "q" {
193
- Some ( value. to_string ( ) )
194
- } else {
195
- None
196
- }
197
- } ) ;
198
-
199
- let response: CratesIoSearchResult = retry_async (
200
- || async {
201
- Ok ( HTTP_CLIENT
202
- . get ( url. clone ( ) )
203
- . send ( )
204
- . await ?
205
- . error_for_status ( ) ?)
206
- } ,
207
- config. crates_io_api_call_retries ,
208
- )
209
- . await ?
210
- . json ( )
211
- . await ?;
212
-
213
- if let Some ( errors) = response. errors {
214
- let messages: Vec < _ > = errors. into_iter ( ) . map ( |e| e. detail ) . collect ( ) ;
215
- bail ! ( "got error from crates.io: {}" , messages. join( "\n " ) ) ;
216
- }
217
-
218
- let Some ( crates) = response. crates else {
219
- bail ! ( "missing releases in crates.io response" ) ;
220
- } ;
221
-
222
- let Some ( meta) = response. meta else {
223
- bail ! ( "missing metadata in crates.io response" ) ;
224
- } ;
148
+ let crate :: registry_api:: Search {
149
+ crates,
150
+ meta,
151
+ executed_query,
152
+ } = registry. get_crates ( query_params) . await ?;
225
153
226
154
let names = Arc :: new (
227
155
crates
@@ -574,6 +502,7 @@ impl_axum_webpage! {
574
502
pub ( crate ) async fn search_handler (
575
503
mut conn : DbConnection ,
576
504
Extension ( config) : Extension < Arc < Config > > ,
505
+ Extension ( registry) : Extension < Arc < RegistryApi > > ,
577
506
Extension ( metrics) : Extension < Arc < InstanceMetrics > > ,
578
507
Query ( mut params) : Query < HashMap < String , String > > ,
579
508
) -> AxumResult < AxumResponse > {
@@ -646,20 +575,21 @@ pub(crate) async fn search_handler(
646
575
AxumNope :: NoResults
647
576
} ) ?;
648
577
let query_params = String :: from_utf8_lossy ( & decoded) ;
649
-
650
- if !query_params. starts_with ( '?' ) {
651
- // sometimes we see plain bytes being passed to `paginate`.
652
- // In these cases we just return `NoResults` and don't call
653
- // the crates.io API.
654
- // The whole point of the `paginate` design is that we don't
655
- // know anything about the pagination args and crates.io can
656
- // change them as they wish, so we cannot do any more checks here.
657
- warn ! (
658
- "didn't get query args in `paginate` arguments for search: \" {}\" " ,
659
- query_params
660
- ) ;
661
- return Err ( AxumNope :: NoResults ) ;
662
- }
578
+ let query_params = match query_params. strip_prefix ( '?' ) {
579
+ Some ( query_params) => query_params,
580
+ None => {
581
+ // sometimes we see plain bytes being passed to `paginate`.
582
+ // In these cases we just return `NoResults` and don't call
583
+ // the crates.io API.
584
+ // The whole point of the `paginate` design is that we don't
585
+ // know anything about the pagination args and crates.io can
586
+ // change them as they wish, so we cannot do any more checks here.
587
+ warn ! (
588
+ "didn't get query args in `paginate` arguments for search: \" {query_params}\" "
589
+ ) ;
590
+ return Err ( AxumNope :: NoResults ) ;
591
+ }
592
+ } ;
663
593
664
594
let mut p = form_urlencoded:: parse ( query_params. as_bytes ( ) ) ;
665
595
if let Some ( v) = p. find_map ( |( k, v) | {
@@ -672,15 +602,15 @@ pub(crate) async fn search_handler(
672
602
sort_by = v;
673
603
} ;
674
604
675
- get_search_results ( & mut conn, & config , & query_params) . await ?
605
+ get_search_results ( & mut conn, & registry , Some ( query_params) ) . await ?
676
606
} else if !query. is_empty ( ) {
677
607
let query_params: String = form_urlencoded:: Serializer :: new ( String :: new ( ) )
678
608
. append_pair ( "q" , & query)
679
609
. append_pair ( "sort" , & sort_by)
680
610
. append_pair ( "per_page" , & RELEASES_IN_RELEASES . to_string ( ) )
681
611
. finish ( ) ;
682
612
683
- get_search_results ( & mut conn, & config , & format ! ( "?{}" , & query_params) ) . await ?
613
+ get_search_results ( & mut conn, & registry , Some ( & query_params) ) . await ?
684
614
} else {
685
615
return Err ( AxumNope :: NoResults ) ;
686
616
} ;
0 commit comments