Skip to content

Commit 3abc2e9

Browse files
committed
Fix review feedback, improve multi-account config, and harden VCR scrubbing
- Fix auth/header handling, pagination, upload behavior, and README examples - Add multi-account spec coverage for per-client configuration - Scrub VCR cassettes and harden VCR filters to avoid leaking credentials
1 parent 576bafc commit 3abc2e9

29 files changed

+197
-106
lines changed

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@ puts stored_file.datetime_stored
799799
# Batch store files using their UUIDs
800800
``` ruby
801801
uuids = ['uuid1', 'uuid2', 'uuid3']
802-
batch_result = Uploadcare::File.batch_store(uuids)
802+
batch_result = Uploadcare::File.batch_store(uuids: uuids)
803803
```
804804

805805
# Check the status of the operation
@@ -823,7 +823,7 @@ unless batch_result.problems.empty?
823823
end
824824
```
825825

826-
## Deleting Files
826+
#### Deleting Files
827827

828828
# Delete a single file
829829
```ruby
@@ -836,12 +836,11 @@ puts deleted_file.datetime_removed
836836
# Batch delete multiple files
837837
```ruby
838838
uuids = ['FILE_UUID_1', 'FILE_UUID_2']
839-
result = Uploadcare::File.batch_delete(uuids)
840839
result = Uploadcare::File.batch_delete(uuids: uuids)
841840
puts result.result
842841
```
843842

844-
## Copying Files
843+
#### Copying Files
845844

846845
# Copy a file to local storage
847846
```ruby

api_examples/upload_api/post_base.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
end
2626

2727
payload = result.success
28-
file_name, file_uuid = payload.first
28+
file_uuid = payload['uuid']
29+
file_name = payload['original_filename']
2930

3031
puts 'File uploaded successfully!'
3132
puts "UUID: #{file_uuid}"

lib/uploadcare/authenticator.rb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,10 @@ def initialize(config:)
4040
# @param body [String] Request body content (default: '')
4141
# @param content_type [String] Content-Type header value (default: 'application/json')
4242
# @return [Hash] Headers hash including authentication
43-
# @raise [Uploadcare::Exception::AuthError] if public key is blank when using secure auth
43+
# @raise [Uploadcare::Exception::AuthError] if public or secret key is blank when using secure auth
4444
def headers(http_method, uri, body = '', content_type = 'application/json')
4545
return simple_auth_headers(content_type) if @config.auth_type == 'Uploadcare.Simple'
46-
if @config.secret_key.nil? || @config.secret_key.empty?
47-
return @default_headers.merge({ 'Content-Type' => content_type })
48-
end
46+
raise Uploadcare::Exception::AuthError, 'Secret Key is blank.' if @config.secret_key.to_s.empty?
4947

5048
validate_public_key
5149
secure_auth_headers(http_method, uri, body, content_type)

lib/uploadcare/clients/file_client.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class Uploadcare::FileClient < Uploadcare::RestClient
55
# Gets list of files without pagination fields
66
# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File/operation/filesList
77
def list(params: {}, request_options: {})
8-
get(path: 'files/', params: params, headers: {}, request_options: request_options)
8+
get(path: '/files/', params: params, headers: {}, request_options: request_options)
99
end
1010

1111
# Stores a file by UUID

lib/uploadcare/clients/file_metadata_client.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def update(uuid:, key:, value:, request_options: {})
3939
encoded_key = URI.encode_www_form_component(key)
4040
put(
4141
path: "/files/#{encoded_uuid}/metadata/#{encoded_key}/",
42-
params: value,
42+
params: value.to_json,
4343
headers: {},
4444
request_options: request_options
4545
)

lib/uploadcare/clients/multipart_uploader_client.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ def upload_chunks(file, links, &block)
6060
# @param link_index [Integer] Index of the current chunk
6161
def process_chunk(file, links, link_index, &chunk_block)
6262
offset = link_index * CHUNK_SIZE
63-
chunk = ::File.read(file, CHUNK_SIZE, offset)
63+
file.seek(offset)
64+
chunk = file.read(CHUNK_SIZE)
6465
Uploadcare::Result.unwrap(put(links[link_index], chunk))
6566

6667
return unless chunk_block

lib/uploadcare/clients/rest_client.rb

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def make_request(method:, path:, params: {}, headers: {}, request_options: {})
7474
# @param path [String] API endpoint path
7575
# @param params [Hash] Request body parameters
7676
# @param headers [Hash] Additional request headers
77-
# @return [Hash] Parsed JSON response body
77+
# @return [Uploadcare::Result] Result wrapper
7878
def post(path:, params: {}, headers: {}, request_options: {})
7979
request(method: :post, path: path, params: params, headers: headers, request_options: request_options)
8080
end
@@ -84,7 +84,7 @@ def post(path:, params: {}, headers: {}, request_options: {})
8484
# @param path [String] API endpoint path
8585
# @param params [Hash] Query parameters
8686
# @param headers [Hash] Additional request headers
87-
# @return [Hash] Parsed JSON response body
87+
# @return [Uploadcare::Result] Result wrapper
8888
def get(path:, params: {}, headers: {}, request_options: {})
8989
request(method: :get, path: path, params: params, headers: headers, request_options: request_options)
9090
end
@@ -94,7 +94,7 @@ def get(path:, params: {}, headers: {}, request_options: {})
9494
# @param path [String] API endpoint path
9595
# @param params [Hash] Request body parameters
9696
# @param headers [Hash] Additional request headers
97-
# @return [Hash] Parsed JSON response body
97+
# @return [Uploadcare::Result] Result wrapper
9898
def put(path:, params: {}, headers: {}, request_options: {})
9999
request(method: :put, path: path, params: params, headers: headers, request_options: request_options)
100100
end
@@ -104,7 +104,7 @@ def put(path:, params: {}, headers: {}, request_options: {})
104104
# @param path [String] API endpoint path
105105
# @param params [Hash] Request body parameters
106106
# @param headers [Hash] Additional request headers
107-
# @return [Hash] Parsed JSON response body
107+
# @return [Uploadcare::Result] Result wrapper
108108
def delete(path:, params: {}, headers: {}, request_options: {})
109109
request(method: :delete, path: path, params: params, headers: headers, request_options: request_options)
110110
end
@@ -148,7 +148,13 @@ def prepare_headers(req, method, uri, params, headers)
148148
body_content = if method == HTTP_GET
149149
''
150150
else
151-
params.nil? || params.empty? ? '' : params.to_json
151+
if params.nil? || (params.respond_to?(:empty?) && params.empty?)
152+
''
153+
elsif params.is_a?(String)
154+
params
155+
else
156+
params.to_json
157+
end
152158
end
153159

154160
content_type = headers['Content-Type'] || authenticator.default_headers['Content-Type']
@@ -163,7 +169,7 @@ def prepare_body_or_params(req, method, params)
163169
else
164170
return if params.nil? || params.empty?
165171

166-
req.body = params.is_a?(String) ? params.to_json : params
172+
req.body = params
167173
end
168174
end
169175

lib/uploadcare/clients/upload_client.rb

Lines changed: 33 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ def post(path:, params: {}, headers: {}, request_options: {})
8282
# @see https://uploadcare.com/api-refs/upload-api/#operation/baseUpload
8383
def upload_file(file:, request_options: {}, **options)
8484
Uploadcare::Result.capture do
85-
raise ArgumentError, 'file must be a File or IO object' unless file.respond_to?(:read)
85+
unless file.respond_to?(:read) && file.respond_to?(:path)
86+
raise ArgumentError, 'file must be a File or IO object with #read and #path'
87+
end
8688

8789
params = build_upload_params(file, options)
8890
Uploadcare::Result.unwrap(post(path: 'base/', params: params, request_options: request_options))
@@ -302,7 +304,9 @@ def multipart_complete(uuid:, request_options: {})
302304
# end
303305
def multipart_upload(file:, request_options: {}, **options, &)
304306
Uploadcare::Result.capture do
305-
raise ArgumentError, 'file must be a File or IO object' unless file.respond_to?(:read)
307+
unless file.respond_to?(:read) && file.respond_to?(:path)
308+
raise ArgumentError, 'file must be a File or IO object with #read and #path'
309+
end
306310

307311
file_size = file.respond_to?(:size) ? file.size : ::File.size(file.path)
308312
filename = file.respond_to?(:original_filename) ? file.original_filename : ::File.basename(file.path)
@@ -621,64 +625,49 @@ def upload_parts_parallel(file, presigned_urls, part_size, threads, &block)
621625
mutex = Mutex.new
622626
queue = Queue.new
623627

624-
# Read all parts into memory (for thread safety)
625-
parts = []
626-
presigned_urls.each_with_index do |presigned_url, index|
627-
file.seek(index * part_size)
628-
part_data = file.read(part_size)
629-
630-
break if part_data.nil? || part_data.empty?
631-
632-
parts << { url: presigned_url, data: part_data, index: index }
633-
end
634-
635-
# Add parts to queue
636-
parts.each { |part| queue << part }
637-
638-
# Add sentinel values (nil) to signal workers to stop
628+
presigned_urls.each_with_index { |url, index| queue << [url, index] }
639629
threads.times { queue << nil }
640630

641-
# Track errors from threads
642631
errors = []
632+
file_path = file.path
643633

644-
# Create worker threads
645634
workers = threads.times.map do
646635
Thread.new do
647-
loop do
648-
part = begin
649-
queue.pop(true)
650-
rescue ThreadError
651-
# Queue is empty, exit cleanly
652-
break
653-
end
636+
worker_file = ::File.open(file_path, 'rb')
637+
begin
638+
loop do
639+
job = begin
640+
queue.pop
641+
rescue ThreadError
642+
break
643+
end
644+
break if job.nil?
645+
646+
presigned_url, index = job
647+
offset = index * part_size
648+
break if offset >= total_size
654649

655-
# Sentinel value signals termination
656-
break if part.nil?
650+
worker_file.seek(offset)
651+
part_data = worker_file.read(part_size)
652+
break if part_data.nil? || part_data.empty?
657653

658-
begin
659-
Uploadcare::Result.unwrap(multipart_upload_part(presigned_url: part[:url], part_data: part[:data]))
654+
Uploadcare::Result.unwrap(multipart_upload_part(presigned_url: presigned_url, part_data: part_data))
660655

661656
mutex.synchronize do
662-
uploaded += part[:data].bytesize
663-
block&.call({ uploaded: uploaded, total: total_size, part: part[:index] + 1,
664-
total_parts: parts.length })
657+
uploaded += part_data.bytesize
658+
block&.call({ uploaded: uploaded, total: total_size, part: index + 1,
659+
total_parts: presigned_urls.length })
665660
end
666-
rescue StandardError => e
667-
mutex.synchronize { errors << e }
668-
raise # Re-raise to terminate thread
669661
end
662+
rescue StandardError => e
663+
mutex.synchronize { errors << e }
664+
ensure
665+
worker_file.close
670666
end
671667
end
672668
end
673669

674-
# Wait for all threads to complete and collect any uncaught exceptions
675-
workers.each do |worker|
676-
worker.join
677-
rescue StandardError => e
678-
mutex.synchronize { errors << e unless errors.include?(e) }
679-
end
680-
681-
# Check for errors and raise the first one
670+
workers.each(&:join)
682671
raise errors.first if errors.any?
683672
end
684673

lib/uploadcare/clients/upload_group_client.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ class Uploadcare::UploadGroupClient < Uploadcare::UploadClient
1010
# @return [Hash] The response containing group information
1111
# @see https://uploadcare.com/api-refs/upload-api/#operation/createFilesGroup
1212
def create_group(uuids:, request_options: {}, **options)
13+
raise ArgumentError, 'uuids must be an array' unless uuids.is_a?(Array)
14+
raise ArgumentError, 'uuids cannot be empty' if uuids.empty?
15+
1316
body_hash = group_body_hash(uuids, options)
1417
post(path: 'group/', params: body_hash, headers: {}, request_options: request_options)
1518
end

lib/uploadcare/clients/uploader_client.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,10 @@ def build_upload_params(files, options = {})
120120
# Convert upload options to API parameters
121121
def upload_options_to_params(options)
122122
params = { 'UPLOADCARE_PUB_KEY' => @config.public_key }
123-
params['UPLOADCARE_STORE'] = store_value(options[:store]) if options[:store]
123+
if options.key?(:store)
124+
store = store_value(options[:store])
125+
params['UPLOADCARE_STORE'] = store unless store.nil?
126+
end
124127
params.merge!(generate_metadata_params(options[:metadata]))
125128
params
126129
end

0 commit comments

Comments
 (0)