2
2
#![ allow( clippy:: identity_op) ] // used for vertical alignment
3
3
4
4
use std:: collections:: BTreeMap ;
5
+ use std:: fmt;
5
6
use std:: fs:: File ;
6
7
use std:: io:: prelude:: * ;
7
8
use std:: io:: { Cursor , SeekFrom } ;
8
9
use std:: time:: Instant ;
9
10
10
- use anyhow:: { bail, Context , Result } ;
11
+ use anyhow:: { bail, format_err , Context , Result } ;
11
12
use curl:: easy:: { Easy , List } ;
12
13
use percent_encoding:: { percent_encode, NON_ALPHANUMERIC } ;
13
14
use serde:: { Deserialize , Serialize } ;
@@ -121,6 +122,70 @@ struct Crates {
121
122
crates : Vec < Crate > ,
122
123
meta : TotalCrates ,
123
124
}
125
+
126
+ #[ derive( Debug ) ]
127
+ pub enum ResponseError {
128
+ Curl ( curl:: Error ) ,
129
+ Api {
130
+ code : u32 ,
131
+ errors : Vec < String > ,
132
+ } ,
133
+ Code {
134
+ code : u32 ,
135
+ headers : Vec < String > ,
136
+ body : String ,
137
+ } ,
138
+ Other ( anyhow:: Error ) ,
139
+ }
140
+
141
+ impl std:: error:: Error for ResponseError {
142
+ fn source ( & self ) -> Option < & ( dyn std:: error:: Error + ' static ) > {
143
+ match self {
144
+ ResponseError :: Curl ( ..) => None ,
145
+ ResponseError :: Api { .. } => None ,
146
+ ResponseError :: Code { .. } => None ,
147
+ ResponseError :: Other ( e) => Some ( e. as_ref ( ) ) ,
148
+ }
149
+ }
150
+ }
151
+
152
+ impl fmt:: Display for ResponseError {
153
+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
154
+ match self {
155
+ ResponseError :: Curl ( e) => write ! ( f, "{}" , e) ,
156
+ ResponseError :: Api { code, errors } => write ! (
157
+ f,
158
+ "api errors (status {} {}): {}" ,
159
+ code,
160
+ reason( * code) ,
161
+ errors. join( ", " )
162
+ ) ,
163
+ ResponseError :: Code {
164
+ code,
165
+ headers,
166
+ body,
167
+ } => write ! (
168
+ f,
169
+ "failed to get a 200 OK response, got {}\n \
170
+ headers:\n \
171
+ \t {}\n \
172
+ body:\n \
173
+ {}",
174
+ code,
175
+ headers. join( "\n \t " ) ,
176
+ body
177
+ ) ,
178
+ ResponseError :: Other ( ..) => write ! ( f, "invalid response from server" ) ,
179
+ }
180
+ }
181
+ }
182
+
183
+ impl From < curl:: Error > for ResponseError {
184
+ fn from ( error : curl:: Error ) -> Self {
185
+ ResponseError :: Curl ( error)
186
+ }
187
+ }
188
+
124
189
impl Registry {
125
190
/// Creates a new `Registry`.
126
191
///
@@ -214,7 +279,25 @@ impl Registry {
214
279
headers. append ( & format ! ( "Authorization: {}" , token) ) ?;
215
280
self . handle . http_headers ( headers) ?;
216
281
217
- let body = self . handle ( & mut |buf| body. read ( buf) . unwrap_or ( 0 ) ) ?;
282
+ let started = Instant :: now ( ) ;
283
+ let body = self
284
+ . handle ( & mut |buf| body. read ( buf) . unwrap_or ( 0 ) )
285
+ . map_err ( |e| match e {
286
+ ResponseError :: Code { code, .. }
287
+ if code == 503
288
+ && started. elapsed ( ) . as_secs ( ) >= 29
289
+ && self . host_is_crates_io ( ) =>
290
+ {
291
+ format_err ! (
292
+ "Request timed out after 30 seconds. If you're trying to \
293
+ upload a crate it may be too large. If the crate is under \
294
+ 10MB in size, you can email [email protected] for assistance.\n \
295
+ Total size was {}.",
296
+ tarball_len
297
+ )
298
+ }
299
+ _ => e. into ( ) ,
300
+ } ) ?;
218
301
219
302
let response = if body. is_empty ( ) {
220
303
"{}" . parse ( ) ?
@@ -308,15 +391,18 @@ impl Registry {
308
391
self . handle . upload ( true ) ?;
309
392
self . handle . in_filesize ( body. len ( ) as u64 ) ?;
310
393
self . handle ( & mut |buf| body. read ( buf) . unwrap_or ( 0 ) )
394
+ . map_err ( |e| e. into ( ) )
311
395
}
312
- None => self . handle ( & mut |_| 0 ) ,
396
+ None => self . handle ( & mut |_| 0 ) . map_err ( |e| e . into ( ) ) ,
313
397
}
314
398
}
315
399
316
- fn handle ( & mut self , read : & mut dyn FnMut ( & mut [ u8 ] ) -> usize ) -> Result < String > {
400
+ fn handle (
401
+ & mut self ,
402
+ read : & mut dyn FnMut ( & mut [ u8 ] ) -> usize ,
403
+ ) -> std:: result:: Result < String , ResponseError > {
317
404
let mut headers = Vec :: new ( ) ;
318
405
let mut body = Vec :: new ( ) ;
319
- let started;
320
406
{
321
407
let mut handle = self . handle . transfer ( ) ;
322
408
handle. read_function ( |buf| Ok ( read ( buf) ) ) ?;
@@ -325,50 +411,36 @@ impl Registry {
325
411
Ok ( data. len ( ) )
326
412
} ) ?;
327
413
handle. header_function ( |data| {
328
- headers. push ( String :: from_utf8_lossy ( data) . into_owned ( ) ) ;
414
+ // Headers contain trailing \r\n, trim them to make it easier
415
+ // to work with.
416
+ let s = String :: from_utf8_lossy ( data) . trim ( ) . to_string ( ) ;
417
+ headers. push ( s) ;
329
418
true
330
419
} ) ?;
331
- started = Instant :: now ( ) ;
332
420
handle. perform ( ) ?;
333
421
}
334
422
335
423
let body = match String :: from_utf8 ( body) {
336
424
Ok ( body) => body,
337
- Err ( ..) => bail ! ( "response body was not valid utf-8" ) ,
425
+ Err ( ..) => {
426
+ return Err ( ResponseError :: Other ( format_err ! (
427
+ "response body was not valid utf-8"
428
+ ) ) )
429
+ }
338
430
} ;
339
431
let errors = serde_json:: from_str :: < ApiErrorList > ( & body)
340
432
. ok ( )
341
433
. map ( |s| s. errors . into_iter ( ) . map ( |s| s. detail ) . collect :: < Vec < _ > > ( ) ) ;
342
434
343
435
match ( self . handle . response_code ( ) ?, errors) {
344
- ( 0 , None ) | ( 200 , None ) => { }
345
- ( 503 , None ) if started. elapsed ( ) . as_secs ( ) >= 29 && self . host_is_crates_io ( ) => bail ! (
346
- "Request timed out after 30 seconds. If you're trying to \
347
- upload a crate it may be too large. If the crate is under \
348
- 10MB in size, you can email [email protected] for assistance."
349
- ) ,
350
- ( code, Some ( errors) ) => {
351
- let reason = reason ( code) ;
352
- bail ! (
353
- "api errors (status {} {}): {}" ,
354
- code,
355
- reason,
356
- errors. join( ", " )
357
- )
358
- }
359
- ( code, None ) => bail ! (
360
- "failed to get a 200 OK response, got {}\n \
361
- headers:\n \
362
- \t {}\n \
363
- body:\n \
364
- {}",
436
+ ( 0 , None ) | ( 200 , None ) => Ok ( body) ,
437
+ ( code, Some ( errors) ) => Err ( ResponseError :: Api { code, errors } ) ,
438
+ ( code, None ) => Err ( ResponseError :: Code {
365
439
code,
366
- headers. join ( " \n \t " ) ,
440
+ headers,
367
441
body,
368
- ) ,
442
+ } ) ,
369
443
}
370
-
371
- Ok ( body)
372
444
}
373
445
}
374
446
0 commit comments