@@ -22,10 +22,13 @@ def initialize(config = Uploadcare.configuration)
2222 @connection = Faraday . new ( url : config . upload_api_root ) do |conn |
2323 conn . request :multipart
2424 conn . request :url_encoded
25- conn . adapter Faraday . default_adapter
2625
2726 # Add response middleware
27+ conn . response :json , content_type : /\b json$/
28+ conn . response :raise_error
2829 conn . response :logger if ENV [ 'DEBUG' ]
30+
31+ conn . adapter Faraday . default_adapter
2932 end
3033 end
3134
@@ -220,7 +223,10 @@ def multipart_upload_part(presigned_url, part_data, options = {})
220223 true
221224 rescue StandardError => e
222225 retries += 1
223- raise "Failed to upload part after #{ max_retries } retries: #{ e . message } " unless retries <= max_retries
226+ unless retries <= max_retries
227+ raise Uploadcare ::Exception ::MultipartUploadError ,
228+ "Failed to upload part after #{ max_retries } retries: #{ e . message } "
229+ end
224230
225231 sleep ( 2 **retries ) # Exponential backoff
226232 retry
@@ -278,7 +284,7 @@ def multipart_complete(uuid)
278284 # client.multipart_upload(file, store: true) do |progress|
279285 # puts "Uploaded #{progress[:uploaded]} / #{progress[:total]} bytes"
280286 # end
281- def multipart_upload ( file , options = { } , &block )
287+ def multipart_upload ( file , options = { } , &)
282288 raise ArgumentError , 'file must be a File or IO object' unless file . respond_to? ( :read )
283289
284290 # Get file information
@@ -296,9 +302,9 @@ def multipart_upload(file, options = {}, &block)
296302 threads = options . fetch ( :threads , 1 )
297303
298304 if threads > 1
299- upload_parts_parallel ( file , presigned_urls , part_size , threads , &block )
305+ upload_parts_parallel ( file , presigned_urls , part_size , threads , &)
300306 else
301- upload_parts_sequential ( file , presigned_urls , part_size , &block )
307+ upload_parts_sequential ( file , presigned_urls , part_size , &)
302308 end
303309
304310 # Complete the upload
@@ -482,14 +488,17 @@ def poll_upload_status(token, options = {})
482488 when 'success'
483489 return status
484490 when 'error'
485- raise "Upload from URL failed: #{ status [ 'error' ] } "
491+ raise Uploadcare :: Exception :: UploadError , "Upload from URL failed: #{ status [ 'error' ] } "
486492 when 'waiting' , 'progress'
487493 elapsed = Time . now - start_time
488- raise "Upload from URL polling timed out after #{ poll_timeout } seconds" if elapsed > poll_timeout
494+ if elapsed > poll_timeout
495+ raise Uploadcare ::Exception ::UploadTimeoutError ,
496+ "Upload from URL polling timed out after #{ poll_timeout } seconds"
497+ end
489498
490499 sleep ( poll_interval )
491500 else
492- raise "Unknown upload status: #{ status [ 'status' ] } "
501+ raise Uploadcare :: Exception :: UnknownStatusError , "Unknown upload status: #{ status [ 'status' ] } "
493502 end
494503 end
495504 end
@@ -542,7 +551,10 @@ def upload_part_to_url(presigned_url, part_data)
542551 req . body = data
543552 end
544553
545- raise "Failed to upload part: HTTP #{ response . status } " unless response . status >= 200 && response . status < 300
554+ unless response . status >= 200 && response . status < 300
555+ raise Uploadcare ::Exception ::MultipartUploadError ,
556+ "Failed to upload part: HTTP #{ response . status } "
557+ end
546558
547559 response
548560 end
@@ -597,43 +609,60 @@ def upload_parts_parallel(file, presigned_urls, part_size, threads, &block)
597609 # Add parts to queue
598610 parts . each { |part | queue << part }
599611
612+ # Track errors from threads
613+ errors = [ ]
614+
600615 # Create worker threads
601616 workers = threads . times . map do
602617 Thread . new do
603618 until queue . empty?
604619 part = begin
605620 queue . pop ( true )
606- rescue StandardError
621+ rescue ThreadError
622+ # Queue is empty, exit cleanly
607623 nil
608624 end
609625 next unless part
610626
611- multipart_upload_part ( part [ :url ] , part [ :data ] )
612-
613- mutex . synchronize do
614- uploaded += part [ :data ] . bytesize
615- block &.call ( { uploaded : uploaded , total : total_size , part : part [ :index ] + 1 ,
616- total_parts : parts . length } )
627+ begin
628+ multipart_upload_part ( part [ :url ] , part [ :data ] )
629+
630+ mutex . synchronize do
631+ uploaded += part [ :data ] . bytesize
632+ block &.call ( { uploaded : uploaded , total : total_size , part : part [ :index ] + 1 ,
633+ total_parts : parts . length } )
634+ end
635+ rescue StandardError => e
636+ mutex . synchronize { errors << e }
637+ raise # Re-raise to terminate thread
617638 end
618639 end
619640 end
620641 end
621642
622643 # Wait for all threads to complete
623644 workers . each ( &:join )
645+
646+ # Check for errors and raise the first one
647+ raise errors . first if errors . any?
624648 end
625649
626650 def success_response? ( response )
627651 response . status >= 200 && response . status < 300
628652 end
629653
630654 def handle_error_response ( response )
631- raise "Upload API error: #{ response . status } #{ response . body } "
655+ raise Uploadcare :: Exception :: UploadError , "Upload API error: #{ response . status } #{ response . body } "
632656 end
633657
634658 def parse_success_response ( response )
635- return { } if response . body . nil? || response . body . strip . empty?
636-
659+ # response.body is already parsed by JSON middleware
660+ return { } if response . body . nil? || ( response . body . is_a? ( String ) && response . body . strip . empty? )
661+
662+ # If it's already a Hash (from JSON middleware), return it directly
663+ return response . body if response . body . is_a? ( Hash )
664+
665+ # Otherwise parse it (for backward compatibility)
637666 JSON . parse ( response . body )
638667 end
639668
@@ -682,9 +711,12 @@ def generate_metadata_params(metadata = nil)
682711
683712 # Handle Faraday-specific errors
684713 def handle_faraday_error ( error )
685- raise "HTTP #{ error . response [ :status ] } : #{ error . response [ :body ] } " if error . response
714+ if error . response
715+ raise Uploadcare ::Exception ::RequestError ,
716+ "HTTP #{ error . response [ :status ] } : #{ error . response [ :body ] } "
717+ end
686718
687- raise "Network error: #{ error . message } "
719+ raise Uploadcare :: Exception :: RequestError , "Network error: #{ error . message } "
688720 end
689721
690722 def form_data_for ( file , params )
0 commit comments