Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ response.reload!
response.reload_until_finished! tries: 300 # default is 600
```

Assembly status rate limits are temporary polling errors. `reload_until_finished!`
keeps polling the original status URL and waits at least the server’s `info.retryIn`
seconds before retrying. The `tries` limit still applies. A status rate limit does
not make `finished?` return `true`; `status_rate_limit?` identifies this response.

In general, you use hash accessor syntax to query any direct attribute from
the [response](https://transloadit.com/docs/api/#assembly-status-response).
Methods suffixed by a question mark provide a more readable way of querying
Expand Down
25 changes: 21 additions & 4 deletions lib/transloadit/response/assembly.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

module Transloadit::Response::Assembly
def reload!
replace Transloadit::Request.new(self["assembly_ssl_url"]).get
url = assembly_ssl_url
raise ArgumentError, "Assembly response does not include a status URL" if url.nil? || url.empty?

replace Transloadit::Request.new(url).get
end

def cancel!
Expand Down Expand Up @@ -34,7 +37,7 @@ def replaying?
end

def finished?
aborted? || canceled? || completed? || error?
aborted? || canceled? || completed? || (error? && !status_rate_limit?)
end

def uploading?
Expand All @@ -45,8 +48,22 @@ def rate_limit?
self["error"] == "RATE_LIMIT_REACHED"
end

def status_rate_limit?
self["error"] == "ASSEMBLY_STATUS_FETCHING_RATE_LIMIT_REACHED"
end

def wait_time
self["info"]["retryIn"] || 0
self["info"]&.fetch("retryIn", 0) || 0
end

def replace(other)
assembly_ssl_url
super
end

# Status rate-limit responses omit this URL, so retain it across reloads.
def assembly_ssl_url
@assembly_ssl_url ||= self["assembly_ssl_url"]
end

DEFAULT_RELOAD_TRIES = 600
Expand All @@ -55,7 +72,7 @@ def reload_until_finished!(options = {})
tries = options[:tries] || DEFAULT_RELOAD_TRIES

tries.times do
sleep 1
sleep(status_rate_limit? ? [1, wait_time].max : 1)
reload!
return self if finished?
end
Expand Down
68 changes: 68 additions & 0 deletions test/unit/transloadit/test_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,74 @@
end
end
end

it "must retry status rate limits at the original URL after retryIn" do
WebMock.reset!
response = Transloadit::Response.new(
body: MultiJson.dump(ok: "ASSEMBLY_EXECUTING", assembly_ssl_url: request_uri),
headers: {}, status: 200
).extend!(Transloadit::Response::Assembly)
WebMock.stub_request(:get, request_uri).to_return(
{status: 429, body: '{"error":"ASSEMBLY_STATUS_FETCHING_RATE_LIMIT_REACHED","info":{"retryIn":5}}'},
{status: 200, body: '{"ok":"ASSEMBLY_COMPLETED"}'}
)
delays = []

with_singleton_method(response, :sleep, proc { |delay| delays << delay }) do
_(response.reload_until_finished!(tries: 2)).must_be_same_as response
end

_(delays).must_equal [1, 5]
_(response.completed?).must_equal true
_(response.assembly_ssl_url).must_equal request_uri
WebMock.assert_requested(:get, request_uri, times: 2)
ensure
WebMock.reset!
end

it "must retain the status URL when manually reloading after a rate limit" do
WebMock.reset!
WebMock.stub_request(:get, request_uri).to_return(
{status: 429, body: '{"error":"ASSEMBLY_STATUS_FETCHING_RATE_LIMIT_REACHED"}'},
{status: 200, body: '{"ok":"ASSEMBLY_COMPLETED"}'}
)

@response.reload!
_(@response.status_rate_limit?).must_equal true
_(@response.finished?).must_equal false
_(@response.wait_time).must_equal 0
@response.reload!

_(@response.completed?).must_equal true
WebMock.assert_requested(:get, request_uri, times: 2)
ensure
WebMock.reset!
end

it "must still stop polling at the tries limit when status requests are rate limited" do
WebMock.reset!
WebMock.stub_request(:get, request_uri).to_return(
status: 429, body: '{"error":"ASSEMBLY_STATUS_FETCHING_RATE_LIMIT_REACHED"}'
)

with_singleton_method(@response, :sleep, proc { |_delay| }) do
assert_raises Transloadit::Exception::ReloadLimitReached do
@response.reload_until_finished!(tries: 2)
end
end

_(@response.finished?).must_equal false
WebMock.assert_requested(:get, request_uri, times: 2)
ensure
WebMock.reset!
end

it "must reject a reload without a status URL before making a request" do
response = Transloadit::Response.new(body: "{}", headers: {}, status: 200)
.extend!(Transloadit::Response::Assembly)

assert_raises(ArgumentError) { response.reload! }
end
end

describe "statuses" do
Expand Down
2 changes: 2 additions & 0 deletions transloadit.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Gem::Specification.new do |gem|

gem.add_dependency "faraday", ">= 2.0", "< 3.0"
gem.add_dependency "faraday-multipart", "~> 1.0"
# Ruby 3.1 uses MultiJson 1.19, whose positional parse options are incompatible with JSON 3.
gem.add_dependency "json", "< 3.0"
gem.add_dependency "multi_json"
gem.add_dependency "mime-types"

Expand Down
Loading