22#![ allow( clippy:: identity_op) ] // used for vertical alignment
33
44use std:: collections:: BTreeMap ;
5+ use std:: fmt;
56use std:: fs:: File ;
67use std:: io:: prelude:: * ;
78use std:: io:: { Cursor , SeekFrom } ;
89use std:: time:: Instant ;
910
10- use anyhow:: { bail, Context , Result } ;
11+ use anyhow:: { bail, format_err , Context , Result } ;
1112use curl:: easy:: { Easy , List } ;
1213use percent_encoding:: { percent_encode, NON_ALPHANUMERIC } ;
1314use serde:: { Deserialize , Serialize } ;
@@ -121,6 +122,70 @@ struct Crates {
121122 crates : Vec < Crate > ,
122123 meta : TotalCrates ,
123124}
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+
124189impl Registry {
125190 /// Creates a new `Registry`.
126191 ///
@@ -214,7 +279,25 @@ impl Registry {
214279 headers. append ( & format ! ( "Authorization: {}" , token) ) ?;
215280 self . handle . http_headers ( headers) ?;
216281
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 help@crates.io for assistance.\n \
295+ Total size was {}.",
296+ tarball_len
297+ )
298+ }
299+ _ => e. into ( ) ,
300+ } ) ?;
218301
219302 let response = if body. is_empty ( ) {
220303 "{}" . parse ( ) ?
@@ -308,15 +391,18 @@ impl Registry {
308391 self . handle . upload ( true ) ?;
309392 self . handle . in_filesize ( body. len ( ) as u64 ) ?;
310393 self . handle ( & mut |buf| body. read ( buf) . unwrap_or ( 0 ) )
394+ . map_err ( |e| e. into ( ) )
311395 }
312- None => self . handle ( & mut |_| 0 ) ,
396+ None => self . handle ( & mut |_| 0 ) . map_err ( |e| e . into ( ) ) ,
313397 }
314398 }
315399
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 > {
317404 let mut headers = Vec :: new ( ) ;
318405 let mut body = Vec :: new ( ) ;
319- let started;
320406 {
321407 let mut handle = self . handle . transfer ( ) ;
322408 handle. read_function ( |buf| Ok ( read ( buf) ) ) ?;
@@ -325,50 +411,36 @@ impl Registry {
325411 Ok ( data. len ( ) )
326412 } ) ?;
327413 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) ;
329418 true
330419 } ) ?;
331- started = Instant :: now ( ) ;
332420 handle. perform ( ) ?;
333421 }
334422
335423 let body = match String :: from_utf8 ( body) {
336424 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+ }
338430 } ;
339431 let errors = serde_json:: from_str :: < ApiErrorList > ( & body)
340432 . ok ( )
341433 . map ( |s| s. errors . into_iter ( ) . map ( |s| s. detail ) . collect :: < Vec < _ > > ( ) ) ;
342434
343435 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 help@crates.io 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 {
365439 code,
366- headers. join ( " \n \t " ) ,
440+ headers,
367441 body,
368- ) ,
442+ } ) ,
369443 }
370-
371- Ok ( body)
372444 }
373445}
374446
0 commit comments