@@ -14,7 +14,7 @@ use crate::{
14
14
} ,
15
15
BuildQueue , Config , InstanceMetrics ,
16
16
} ;
17
- use anyhow:: { anyhow, Context as _, Result } ;
17
+ use anyhow:: { anyhow, bail , Context as _, Result } ;
18
18
use axum:: {
19
19
extract:: { Extension , Path , Query } ,
20
20
response:: { IntoResponse , Response as AxumResponse } ,
@@ -141,10 +141,15 @@ async fn get_search_results(
141
141
config : & Config ,
142
142
query_params : & str ,
143
143
) -> Result < SearchResult , anyhow:: Error > {
144
+ #[ derive( Deserialize ) ]
145
+ struct CratesIoError {
146
+ detail : String ,
147
+ }
144
148
#[ derive( Deserialize ) ]
145
149
struct CratesIoSearchResult {
146
- crates : Vec < CratesIoCrate > ,
147
- meta : CratesIoMeta ,
150
+ crates : Option < Vec < CratesIoCrate > > ,
151
+ meta : Option < CratesIoMeta > ,
152
+ errors : Option < Vec < CratesIoError > > ,
148
153
}
149
154
#[ derive( Deserialize , Debug ) ]
150
155
struct CratesIoCrate {
@@ -186,7 +191,7 @@ async fn get_search_results(
186
191
}
187
192
} ) ;
188
193
189
- let releases : CratesIoSearchResult = retry_async (
194
+ let response : CratesIoSearchResult = retry_async (
190
195
|| async {
191
196
Ok ( HTTP_CLIENT
192
197
. get ( url. clone ( ) )
@@ -200,9 +205,21 @@ async fn get_search_results(
200
205
. json ( )
201
206
. await ?;
202
207
208
+ if let Some ( errors) = response. errors {
209
+ let messages: Vec < _ > = errors. into_iter ( ) . map ( |e| e. detail ) . collect ( ) ;
210
+ bail ! ( "got error from crates.io: {}" , messages. join( "\n " ) ) ;
211
+ }
212
+
213
+ let Some ( crates) = response. crates else {
214
+ bail ! ( "missing releases in crates.io response" ) ;
215
+ } ;
216
+
217
+ let Some ( meta) = response. meta else {
218
+ bail ! ( "missing metadata in crates.io response" ) ;
219
+ } ;
220
+
203
221
let names = Arc :: new (
204
- releases
205
- . crates
222
+ crates
206
223
. into_iter ( )
207
224
. map ( |krate| krate. name )
208
225
. collect :: < Vec < _ > > ( ) ,
@@ -261,8 +278,8 @@ async fn get_search_results(
261
278
. cloned ( )
262
279
. collect ( ) ,
263
280
executed_query,
264
- prev_page : releases . meta . prev_page ,
265
- next_page : releases . meta . next_page ,
281
+ prev_page : meta. prev_page ,
282
+ next_page : meta. next_page ,
266
283
} )
267
284
}
268
285
@@ -1034,6 +1051,45 @@ mod tests {
1034
1051
} )
1035
1052
}
1036
1053
1054
+ #[ test]
1055
+ fn crates_io_errors_as_status_code_200 ( ) {
1056
+ wrapper ( |env| {
1057
+ let mut crates_io = mockito:: Server :: new ( ) ;
1058
+ env. override_config ( |config| {
1059
+ config. crates_io_api_call_retries = 0 ;
1060
+ config. registry_api_host = crates_io. url ( ) . parse ( ) . unwrap ( ) ;
1061
+ } ) ;
1062
+
1063
+ let _m = crates_io
1064
+ . mock ( "GET" , "/api/v1/crates" )
1065
+ . match_query ( Matcher :: AllOf ( vec ! [
1066
+ Matcher :: UrlEncoded ( "q" . into( ) , "doesnt_matter_here" . into( ) ) ,
1067
+ Matcher :: UrlEncoded ( "per_page" . into( ) , "30" . into( ) ) ,
1068
+ ] ) )
1069
+ . with_status ( 200 )
1070
+ . with_header ( "content-type" , "application/json" )
1071
+ . with_body (
1072
+ json ! ( {
1073
+ "errors" : [
1074
+ { "detail" : "error name 1" } ,
1075
+ { "detail" : "error name 2" } ,
1076
+ ]
1077
+ } )
1078
+ . to_string ( ) ,
1079
+ )
1080
+ . create ( ) ;
1081
+
1082
+ let response = env
1083
+ . frontend ( )
1084
+ . get ( "/releases/search?query=doesnt_matter_here" )
1085
+ . send ( ) ?;
1086
+ assert_eq ! ( response. status( ) , 500 ) ;
1087
+
1088
+ assert ! ( response. text( ) ?. contains( "error name 1\n error name 2" ) ) ;
1089
+ Ok ( ( ) )
1090
+ } )
1091
+ }
1092
+
1037
1093
#[ test_case( StatusCode :: NOT_FOUND ) ]
1038
1094
#[ test_case( StatusCode :: INTERNAL_SERVER_ERROR ) ]
1039
1095
#[ test_case( StatusCode :: BAD_GATEWAY ) ]
0 commit comments