33use crate :: {
44 build_queue:: { QueuedCrate , REBUILD_PRIORITY } ,
55 cdn, impl_axum_webpage,
6- utils:: { report_error, retry_async } ,
6+ utils:: report_error,
77 web:: {
88 axum_parse_uri_with_params, axum_redirect, encode_url_path,
99 error:: { AxumNope , AxumResult } ,
@@ -12,9 +12,9 @@ use crate::{
1212 page:: templates:: { filters, RenderRegular , RenderSolid } ,
1313 ReqVersion ,
1414 } ,
15- AsyncBuildQueue , Config , InstanceMetrics ,
15+ AsyncBuildQueue , Config , InstanceMetrics , RegistryApi ,
1616} ;
17- use anyhow:: { anyhow, bail , Context as _, Result } ;
17+ use anyhow:: { anyhow, Context as _, Result } ;
1818use axum:: {
1919 extract:: { Extension , Query } ,
2020 response:: { IntoResponse , Response as AxumResponse } ,
@@ -23,14 +23,13 @@ use base64::{engine::general_purpose::STANDARD as b64, Engine};
2323use chrono:: { DateTime , Utc } ;
2424use futures_util:: stream:: TryStreamExt ;
2525use itertools:: Itertools ;
26- use once_cell:: sync:: Lazy ;
2726use rinja:: Template ;
2827use serde:: { Deserialize , Serialize } ;
2928use sqlx:: Row ;
3029use std:: collections:: { BTreeMap , HashMap , HashSet } ;
3130use std:: str;
3231use std:: sync:: Arc ;
33- use tracing:: { debug , warn} ;
32+ use tracing:: warn;
3433use url:: form_urlencoded;
3534
3635use super :: cache:: CachePolicy ;
@@ -143,85 +142,14 @@ struct SearchResult {
143142/// This delegates to the crates.io search API.
144143async fn get_search_results (
145144 conn : & mut sqlx:: PgConnection ,
146- config : & Config ,
147- query_params : & str ,
145+ registry : & RegistryApi ,
146+ query_params : Option < & str > ,
148147) -> 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 ?;
225153
226154 let names = Arc :: new (
227155 crates
@@ -574,6 +502,7 @@ impl_axum_webpage! {
574502pub ( crate ) async fn search_handler (
575503 mut conn : DbConnection ,
576504 Extension ( config) : Extension < Arc < Config > > ,
505+ Extension ( registry) : Extension < Arc < RegistryApi > > ,
577506 Extension ( metrics) : Extension < Arc < InstanceMetrics > > ,
578507 Query ( mut params) : Query < HashMap < String , String > > ,
579508) -> AxumResult < AxumResponse > {
@@ -646,20 +575,21 @@ pub(crate) async fn search_handler(
646575 AxumNope :: NoResults
647576 } ) ?;
648577 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+ } ;
663593
664594 let mut p = form_urlencoded:: parse ( query_params. as_bytes ( ) ) ;
665595 if let Some ( v) = p. find_map ( |( k, v) | {
@@ -672,15 +602,15 @@ pub(crate) async fn search_handler(
672602 sort_by = v;
673603 } ;
674604
675- get_search_results ( & mut conn, & config , & query_params) . await ?
605+ get_search_results ( & mut conn, & registry , Some ( query_params) ) . await ?
676606 } else if !query. is_empty ( ) {
677607 let query_params: String = form_urlencoded:: Serializer :: new ( String :: new ( ) )
678608 . append_pair ( "q" , & query)
679609 . append_pair ( "sort" , & sort_by)
680610 . append_pair ( "per_page" , & RELEASES_IN_RELEASES . to_string ( ) )
681611 . finish ( ) ;
682612
683- get_search_results ( & mut conn, & config , & format ! ( "?{}" , & query_params) ) . await ?
613+ get_search_results ( & mut conn, & registry , Some ( & query_params) ) . await ?
684614 } else {
685615 return Err ( AxumNope :: NoResults ) ;
686616 } ;
0 commit comments