33use crate :: {
44 build_queue:: QueuedCrate ,
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,24 +12,23 @@ 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 } ,
2121} ;
2222use base64:: { engine:: general_purpose:: STANDARD as b64, Engine } ;
2323use chrono:: { DateTime , Utc } ;
2424use futures_util:: stream:: TryStreamExt ;
25- use once_cell:: sync:: Lazy ;
2625use rinja:: Template ;
27- use serde:: { Deserialize , Serialize } ;
26+ use serde:: Serialize ;
2827use sqlx:: Row ;
2928use std:: collections:: { BTreeMap , HashMap , HashSet } ;
3029use std:: str;
3130use std:: sync:: Arc ;
32- use tracing:: { debug , warn} ;
31+ use tracing:: warn;
3332use url:: form_urlencoded;
3433
3534use super :: cache:: CachePolicy ;
@@ -142,85 +141,14 @@ struct SearchResult {
142141/// This delegates to the crates.io search API.
143142async fn get_search_results (
144143 conn : & mut sqlx:: PgConnection ,
145- config : & Config ,
146- query_params : & str ,
144+ registry : & RegistryApi ,
145+ query_params : Option < & str > ,
147146) -> Result < SearchResult , anyhow:: Error > {
148- #[ derive( Deserialize ) ]
149- struct CratesIoError {
150- detail : String ,
151- }
152- #[ derive( Deserialize ) ]
153- struct CratesIoSearchResult {
154- crates : Option < Vec < CratesIoCrate > > ,
155- meta : Option < CratesIoMeta > ,
156- errors : Option < Vec < CratesIoError > > ,
157- }
158- #[ derive( Deserialize , Debug ) ]
159- struct CratesIoCrate {
160- name : String ,
161- }
162- #[ derive( Deserialize , Debug ) ]
163- struct CratesIoMeta {
164- next_page : Option < String > ,
165- prev_page : Option < String > ,
166- }
167-
168- use crate :: utils:: APP_USER_AGENT ;
169- use reqwest:: header:: { HeaderMap , HeaderValue , ACCEPT , USER_AGENT } ;
170- use reqwest:: Client as HttpClient ;
171-
172- static HTTP_CLIENT : Lazy < HttpClient > = Lazy :: new ( || {
173- let mut headers = HeaderMap :: new ( ) ;
174- headers. insert ( USER_AGENT , HeaderValue :: from_static ( APP_USER_AGENT ) ) ;
175- headers. insert ( ACCEPT , HeaderValue :: from_static ( "application/json" ) ) ;
176- HttpClient :: builder ( )
177- . default_headers ( headers)
178- . build ( )
179- . unwrap ( )
180- } ) ;
181-
182- let url = config
183- . registry_api_host
184- . join ( & format ! ( "api/v1/crates{query_params}" ) ) ?;
185- debug ! ( "fetching search results from {}" , url) ;
186-
187- // extract the query from the query args.
188- // This is easier because the query might have been encoded in the bash64-encoded
189- // paginate parameter.
190- let executed_query = url. query_pairs ( ) . find_map ( |( key, value) | {
191- if key == "q" {
192- Some ( value. to_string ( ) )
193- } else {
194- None
195- }
196- } ) ;
197-
198- let response: CratesIoSearchResult = retry_async (
199- || async {
200- Ok ( HTTP_CLIENT
201- . get ( url. clone ( ) )
202- . send ( )
203- . await ?
204- . error_for_status ( ) ?)
205- } ,
206- config. crates_io_api_call_retries ,
207- )
208- . await ?
209- . json ( )
210- . await ?;
211-
212- if let Some ( errors) = response. errors {
213- let messages: Vec < _ > = errors. into_iter ( ) . map ( |e| e. detail ) . collect ( ) ;
214- bail ! ( "got error from crates.io: {}" , messages. join( "\n " ) ) ;
215- }
216-
217- let Some ( crates) = response. crates else {
218- bail ! ( "missing releases in crates.io response" ) ;
219- } ;
220-
221- let Some ( meta) = response. meta else {
222- bail ! ( "missing metadata in crates.io response" ) ;
223- } ;
147+ let crate :: registry_api:: Search {
148+ crates,
149+ meta,
150+ executed_query,
151+ } = registry. get_crates ( query_params) . await ?;
224152
225153 let names = Arc :: new (
226154 crates
@@ -573,6 +501,7 @@ impl_axum_webpage! {
573501pub ( crate ) async fn search_handler (
574502 mut conn : DbConnection ,
575503 Extension ( config) : Extension < Arc < Config > > ,
504+ Extension ( registry) : Extension < Arc < RegistryApi > > ,
576505 Extension ( metrics) : Extension < Arc < InstanceMetrics > > ,
577506 Query ( mut params) : Query < HashMap < String , String > > ,
578507) -> AxumResult < AxumResponse > {
@@ -645,20 +574,21 @@ pub(crate) async fn search_handler(
645574 AxumNope :: NoResults
646575 } ) ?;
647576 let query_params = String :: from_utf8_lossy ( & decoded) ;
648-
649- if !query_params. starts_with ( '?' ) {
650- // sometimes we see plain bytes being passed to `paginate`.
651- // In these cases we just return `NoResults` and don't call
652- // the crates.io API.
653- // The whole point of the `paginate` design is that we don't
654- // know anything about the pagination args and crates.io can
655- // change them as they wish, so we cannot do any more checks here.
656- warn ! (
657- "didn't get query args in `paginate` arguments for search: \" {}\" " ,
658- query_params
659- ) ;
660- return Err ( AxumNope :: NoResults ) ;
661- }
577+ let query_params = match query_params. strip_prefix ( '?' ) {
578+ Some ( query_params) => query_params,
579+ None => {
580+ // sometimes we see plain bytes being passed to `paginate`.
581+ // In these cases we just return `NoResults` and don't call
582+ // the crates.io API.
583+ // The whole point of the `paginate` design is that we don't
584+ // know anything about the pagination args and crates.io can
585+ // change them as they wish, so we cannot do any more checks here.
586+ warn ! (
587+ "didn't get query args in `paginate` arguments for search: \" {query_params}\" "
588+ ) ;
589+ return Err ( AxumNope :: NoResults ) ;
590+ }
591+ } ;
662592
663593 let mut p = form_urlencoded:: parse ( query_params. as_bytes ( ) ) ;
664594 if let Some ( v) = p. find_map ( |( k, v) | {
@@ -671,15 +601,15 @@ pub(crate) async fn search_handler(
671601 sort_by = v;
672602 } ;
673603
674- get_search_results ( & mut conn, & config , & query_params) . await ?
604+ get_search_results ( & mut conn, & registry , Some ( query_params) ) . await ?
675605 } else if !query. is_empty ( ) {
676606 let query_params: String = form_urlencoded:: Serializer :: new ( String :: new ( ) )
677607 . append_pair ( "q" , & query)
678608 . append_pair ( "sort" , & sort_by)
679609 . append_pair ( "per_page" , & RELEASES_IN_RELEASES . to_string ( ) )
680610 . finish ( ) ;
681611
682- get_search_results ( & mut conn, & config , & format ! ( "?{}" , & query_params) ) . await ?
612+ get_search_results ( & mut conn, & registry , Some ( & query_params) ) . await ?
683613 } else {
684614 return Err ( AxumNope :: NoResults ) ;
685615 } ;
0 commit comments