transloadit 3.1.2 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitattributes +44 -5
- data/.github/workflows/ci.yml +18 -15
- data/.gitignore +1 -1
- data/.standard.yml +2 -0
- data/CHANGELOG.md +17 -0
- data/Dockerfile +1 -1
- data/Gemfile +4 -0
- data/MIGRATING_TO_V4.md +136 -0
- data/README.md +55 -3
- data/lib/transloadit/assembly.rb +73 -14
- data/lib/transloadit/exception.rb +14 -6
- data/lib/transloadit/request.rb +54 -22
- data/lib/transloadit/response.rb +36 -34
- data/lib/transloadit/version.rb +1 -1
- data/lib/transloadit.rb +5 -1
- data/test/test_helper.rb +26 -0
- data/test/unit/test_transloadit.rb +1 -1
- data/test/unit/transloadit/test_assembly.rb +92 -9
- data/test/unit/transloadit/test_assembly_uploads.rb +272 -0
- data/test/unit/transloadit/test_request.rb +1 -19
- data/test/unit/transloadit/test_response.rb +33 -31
- data/test/unit/transloadit/test_transport_compatibility.rb +222 -0
- data/transloadit.gemspec +5 -4
- metadata +34 -14
data/lib/transloadit/request.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
require "transloadit"
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "faraday/multipart"
|
|
5
|
+
require "mime/types"
|
|
4
6
|
require "openssl"
|
|
5
7
|
|
|
6
8
|
#
|
|
@@ -47,7 +49,7 @@ class Transloadit::Request
|
|
|
47
49
|
#
|
|
48
50
|
def get(params = {})
|
|
49
51
|
request! do
|
|
50
|
-
api
|
|
52
|
+
api.get(url.path + to_query(params), nil, API_HEADERS)
|
|
51
53
|
end
|
|
52
54
|
end
|
|
53
55
|
|
|
@@ -60,8 +62,10 @@ class Transloadit::Request
|
|
|
60
62
|
#
|
|
61
63
|
def delete(payload = {})
|
|
62
64
|
request! do
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
api.delete(url.path) do |request|
|
|
66
|
+
request.headers.update(API_HEADERS)
|
|
67
|
+
request.body = to_payload(payload)
|
|
68
|
+
end
|
|
65
69
|
end
|
|
66
70
|
end
|
|
67
71
|
|
|
@@ -74,7 +78,7 @@ class Transloadit::Request
|
|
|
74
78
|
#
|
|
75
79
|
def post(payload = {})
|
|
76
80
|
request! do
|
|
77
|
-
api
|
|
81
|
+
api.post(url.path, to_payload(payload), API_HEADERS)
|
|
78
82
|
end
|
|
79
83
|
end
|
|
80
84
|
|
|
@@ -87,7 +91,7 @@ class Transloadit::Request
|
|
|
87
91
|
#
|
|
88
92
|
def put(payload = {})
|
|
89
93
|
request! do
|
|
90
|
-
api
|
|
94
|
+
api.put(url.path, to_payload(payload), API_HEADERS)
|
|
91
95
|
end
|
|
92
96
|
end
|
|
93
97
|
|
|
@@ -118,12 +122,20 @@ class Transloadit::Request
|
|
|
118
122
|
# hostname, then the hostname is used as the base endpoint of the API.
|
|
119
123
|
# Otherwise uses the class-level API base.
|
|
120
124
|
#
|
|
121
|
-
# @return [
|
|
125
|
+
# @return [Faraday::Connection] the API endpoint for this instance
|
|
122
126
|
#
|
|
123
|
-
def api
|
|
127
|
+
def api
|
|
124
128
|
@api ||= case url.host
|
|
125
|
-
when String then
|
|
126
|
-
else
|
|
129
|
+
when String then connection_for("#{url.scheme}://#{url.host}")
|
|
130
|
+
else connection_for("#{API_ENDPOINT.scheme}://#{API_ENDPOINT.host}")
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def connection_for(base_url)
|
|
135
|
+
Faraday.new(url: base_url) do |connection|
|
|
136
|
+
connection.request :multipart
|
|
137
|
+
connection.request :url_encoded
|
|
138
|
+
connection.adapter Faraday.default_adapter
|
|
127
139
|
end
|
|
128
140
|
end
|
|
129
141
|
|
|
@@ -151,12 +163,33 @@ class Transloadit::Request
|
|
|
151
163
|
sig = signature(new_payload[:params])
|
|
152
164
|
new_payload[:signature] = sig unless sig.nil?
|
|
153
165
|
|
|
154
|
-
|
|
155
|
-
|
|
166
|
+
payload.each do |key, value|
|
|
167
|
+
next if key == :params || key == :signature
|
|
168
|
+
|
|
169
|
+
new_payload[key] = upload?(value) ? multipart_file(value) : value
|
|
170
|
+
end
|
|
156
171
|
|
|
157
172
|
new_payload
|
|
158
173
|
end
|
|
159
174
|
|
|
175
|
+
def upload?(value)
|
|
176
|
+
value.respond_to?(:read)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def multipart_file(file)
|
|
180
|
+
path = file.path if file.respond_to?(:path)
|
|
181
|
+
filename = file.original_filename if file.respond_to?(:original_filename)
|
|
182
|
+
filename = File.basename(path) if filename.to_s.empty? && path
|
|
183
|
+
filename = "upload" if filename.to_s.empty?
|
|
184
|
+
|
|
185
|
+
content_type = file.content_type if file.respond_to?(:content_type)
|
|
186
|
+
content_type = MIME::Types.type_for(path).first&.content_type if content_type.to_s.empty? && path
|
|
187
|
+
content_type = "application/octet-stream" if content_type.to_s.empty?
|
|
188
|
+
|
|
189
|
+
source = path || file
|
|
190
|
+
Faraday::Multipart::FilePart.new(source, content_type, filename)
|
|
191
|
+
end
|
|
192
|
+
|
|
160
193
|
#
|
|
161
194
|
# Updates the GET/DELETE params hash to be compliant with the Transloadit
|
|
162
195
|
# API by URI escaping and encoding the params hash, and attaching a
|
|
@@ -181,18 +214,17 @@ class Transloadit::Request
|
|
|
181
214
|
end
|
|
182
215
|
|
|
183
216
|
#
|
|
184
|
-
# Wraps a request's
|
|
185
|
-
# is raised by RestClient.
|
|
217
|
+
# Wraps a request's result in a Transloadit::Response.
|
|
186
218
|
#
|
|
187
219
|
def request!(&request)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
Transloadit::
|
|
220
|
+
response = yield
|
|
221
|
+
Transloadit::Response.new(
|
|
222
|
+
body: response.body,
|
|
223
|
+
headers: response.headers,
|
|
224
|
+
status: response.status
|
|
225
|
+
)
|
|
226
|
+
rescue Faraday::Error
|
|
227
|
+
raise Transloadit::Exception::RequestFailed, "Transloadit request failed"
|
|
196
228
|
end
|
|
197
229
|
|
|
198
230
|
#
|
data/lib/transloadit/response.rb
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
require "transloadit"
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require "delegate"
|
|
5
|
-
|
|
6
|
-
class Transloadit::Response < Delegator
|
|
3
|
+
class Transloadit::Response
|
|
7
4
|
autoload :Assembly, "transloadit/response/assembly"
|
|
8
5
|
|
|
9
6
|
#
|
|
10
|
-
# Creates
|
|
7
|
+
# Creates a response without exposing the underlying HTTP client.
|
|
11
8
|
#
|
|
12
|
-
# @param [
|
|
9
|
+
# @param [String] body the raw response body
|
|
10
|
+
# @param [Hash] headers the response headers
|
|
11
|
+
# @param [Integer] status the HTTP response status
|
|
13
12
|
#
|
|
14
|
-
def initialize(
|
|
15
|
-
|
|
13
|
+
def initialize(body:, headers:, status:)
|
|
14
|
+
@raw_body = body
|
|
15
|
+
@headers = normalize_headers(headers)
|
|
16
|
+
@status = status
|
|
16
17
|
end
|
|
17
18
|
|
|
19
|
+
# @return [Hash] normalized response headers
|
|
20
|
+
attr_reader :headers
|
|
21
|
+
|
|
22
|
+
# @return [Integer] the HTTP response status
|
|
23
|
+
attr_reader :status
|
|
24
|
+
|
|
25
|
+
# RestClient exposed the status through +code+ in previous SDK versions.
|
|
26
|
+
alias_method :code, :status
|
|
27
|
+
|
|
18
28
|
#
|
|
19
29
|
# Returns the attribute from the JSON response.
|
|
20
30
|
#
|
|
@@ -31,7 +41,7 @@ class Transloadit::Response < Delegator
|
|
|
31
41
|
# @return [Hash] the parsed JSON body hash
|
|
32
42
|
#
|
|
33
43
|
def body
|
|
34
|
-
MultiJson.load
|
|
44
|
+
MultiJson.load @raw_body
|
|
35
45
|
end
|
|
36
46
|
|
|
37
47
|
#
|
|
@@ -56,36 +66,28 @@ class Transloadit::Response < Delegator
|
|
|
56
66
|
self
|
|
57
67
|
end
|
|
58
68
|
|
|
59
|
-
protected
|
|
60
|
-
|
|
61
|
-
#
|
|
62
|
-
# The object to delegate method calls to.
|
|
63
|
-
#
|
|
64
|
-
# @return [RestClient::Response]
|
|
65
|
-
#
|
|
66
|
-
def __getobj__
|
|
67
|
-
@response
|
|
68
|
-
end
|
|
69
|
-
|
|
70
69
|
#
|
|
71
|
-
#
|
|
70
|
+
# Replaces this response's HTTP data with another response's data.
|
|
72
71
|
#
|
|
73
|
-
# @param [
|
|
74
|
-
# @return [RestClient::Response] the delegated response
|
|
75
|
-
#
|
|
76
|
-
def __setobj__(response)
|
|
77
|
-
@response = response
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
#
|
|
81
|
-
# Replaces the object this instance delegates to with the one the other
|
|
82
|
-
# object uses.
|
|
83
|
-
#
|
|
84
|
-
# @param [Delegator] other the object whose delegate to use
|
|
72
|
+
# @param [Transloadit::Response] other the response whose data to use
|
|
85
73
|
# @return [Transloadit::Response] this response
|
|
86
74
|
#
|
|
87
75
|
def replace(other)
|
|
88
|
-
|
|
76
|
+
@raw_body = other.raw_body
|
|
77
|
+
@headers = other.headers
|
|
78
|
+
@status = other.status
|
|
89
79
|
self
|
|
90
80
|
end
|
|
81
|
+
|
|
82
|
+
protected
|
|
83
|
+
|
|
84
|
+
attr_reader :raw_body
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def normalize_headers(headers)
|
|
89
|
+
headers.to_h.each_with_object({}) do |(name, value), normalized|
|
|
90
|
+
normalized[name.to_s.downcase.tr("-", "_").to_sym] = value
|
|
91
|
+
end
|
|
92
|
+
end
|
|
91
93
|
end
|
data/lib/transloadit/version.rb
CHANGED
data/lib/transloadit.rb
CHANGED
|
@@ -2,7 +2,7 @@ require "multi_json"
|
|
|
2
2
|
require "date"
|
|
3
3
|
require "json"
|
|
4
4
|
require "openssl"
|
|
5
|
-
require "cgi"
|
|
5
|
+
require "cgi/escape"
|
|
6
6
|
require "uri"
|
|
7
7
|
|
|
8
8
|
#
|
|
@@ -79,6 +79,10 @@ class Transloadit
|
|
|
79
79
|
# @option options [String] :template_id the ID of a
|
|
80
80
|
# {template}[https://transloadit.com/templates] to use instead of
|
|
81
81
|
# specifying options here directly
|
|
82
|
+
# @option options [Hash] :fields custom metadata, retained as typed JSON;
|
|
83
|
+
# passing upload IOs here is deprecated in favor of :uploads
|
|
84
|
+
# @option options [Hash<String, IO>] :uploads named multipart uploads, kept
|
|
85
|
+
# separate from the signed JSON Instructions
|
|
82
86
|
#
|
|
83
87
|
def assembly(options = {})
|
|
84
88
|
Transloadit::Assembly.new(self, options)
|
data/test/test_helper.rb
CHANGED
|
@@ -72,3 +72,29 @@ module TransloaditCliHelpers
|
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
Minitest::Test.include(TransloaditCliHelpers)
|
|
75
|
+
|
|
76
|
+
module SingletonMethodOverrideHelpers
|
|
77
|
+
def with_singleton_method(target, name, implementation)
|
|
78
|
+
eigenclass = target.singleton_class
|
|
79
|
+
|
|
80
|
+
backup = "__orig_#{name}_for_test__"
|
|
81
|
+
had_method = eigenclass.method_defined?(name) || eigenclass.private_method_defined?(name)
|
|
82
|
+
eigenclass.send(:alias_method, backup, name) if had_method
|
|
83
|
+
|
|
84
|
+
target.define_singleton_method(name, &implementation)
|
|
85
|
+
yield
|
|
86
|
+
ensure
|
|
87
|
+
eigenclass = target.singleton_class
|
|
88
|
+
if eigenclass.method_defined?(name) || eigenclass.private_method_defined?(name)
|
|
89
|
+
eigenclass.send(:remove_method, name)
|
|
90
|
+
end
|
|
91
|
+
if had_method
|
|
92
|
+
eigenclass.send(:alias_method, name, backup)
|
|
93
|
+
if eigenclass.method_defined?(backup) || eigenclass.private_method_defined?(backup)
|
|
94
|
+
eigenclass.send(:remove_method, backup)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
Minitest::Test.include(SingletonMethodOverrideHelpers)
|
|
@@ -109,7 +109,7 @@ describe Transloadit do
|
|
|
109
109
|
|
|
110
110
|
it "must produce Transloadit-compatible JSON output" do
|
|
111
111
|
fixed_time = Time.utc(2025, 10, 28, 0, 0, 0)
|
|
112
|
-
Time
|
|
112
|
+
with_singleton_method(Time, :now, proc { fixed_time }) do
|
|
113
113
|
_(@transloadit.to_json).must_equal MultiJson.dump(@transloadit.to_hash)
|
|
114
114
|
end
|
|
115
115
|
end
|
|
@@ -64,9 +64,13 @@ describe Transloadit::Assembly do
|
|
|
64
64
|
|
|
65
65
|
it "must send the signature before any file" do
|
|
66
66
|
transloadit = Transloadit.new(key: "", secret: "foo")
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
File.open("lib/transloadit/version.rb") do |file|
|
|
68
|
+
Transloadit::Assembly.new(
|
|
69
|
+
transloadit
|
|
70
|
+
).create! file
|
|
71
|
+
|
|
72
|
+
_(file.closed?).must_equal false
|
|
73
|
+
end
|
|
70
74
|
|
|
71
75
|
assert_requested(:post, "https://api2.transloadit.com/assemblies") do |req|
|
|
72
76
|
position_params = req.body.index 'name="params"'
|
|
@@ -76,6 +80,7 @@ describe Transloadit::Assembly do
|
|
|
76
80
|
_(position_params).wont_be_nil
|
|
77
81
|
_(position_signature).wont_be_nil
|
|
78
82
|
_(position_file).wont_be_nil
|
|
83
|
+
_(req.body).must_include 'filename="version.rb"'
|
|
79
84
|
|
|
80
85
|
_(position_params < position_signature).must_equal true
|
|
81
86
|
_(position_signature < position_file).must_equal true
|
|
@@ -116,7 +121,7 @@ describe Transloadit::Assembly do
|
|
|
116
121
|
|
|
117
122
|
assert_requested(:post, "https://api2.transloadit.com/assemblies") do |req|
|
|
118
123
|
values = values_from_post_body(req.body)
|
|
119
|
-
_(values
|
|
124
|
+
_(values.keys).must_equal ["params"]
|
|
120
125
|
_(MultiJson.load(values["params"])["fields"]["tag"]).must_equal "ninja-cat"
|
|
121
126
|
end
|
|
122
127
|
end
|
|
@@ -146,12 +151,14 @@ describe Transloadit::Assembly do
|
|
|
146
151
|
it "must call the create! method with the same parameters" do
|
|
147
152
|
VCR.use_cassette "submit_assembly" do
|
|
148
153
|
file = open("lib/transloadit/version.rb")
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
154
|
+
create_arg = nil
|
|
155
|
+
with_singleton_method(@assembly, :create!, proc { |arg|
|
|
156
|
+
create_arg = arg
|
|
157
|
+
nil
|
|
158
|
+
}) do
|
|
152
159
|
@assembly.submit!(file)
|
|
153
160
|
end
|
|
154
|
-
|
|
161
|
+
_(create_arg).must_equal file
|
|
155
162
|
end
|
|
156
163
|
end
|
|
157
164
|
end
|
|
@@ -171,9 +178,12 @@ describe Transloadit::Assembly do
|
|
|
171
178
|
@assembly.options[:tries] = 1
|
|
172
179
|
|
|
173
180
|
VCR.use_cassette "rate_limit_succeed" do
|
|
174
|
-
assert_raises Transloadit::Exception::RateLimitReached do
|
|
181
|
+
error = assert_raises Transloadit::Exception::RateLimitReached do
|
|
175
182
|
@assembly.create! open("lib/transloadit/version.rb")
|
|
176
183
|
end
|
|
184
|
+
|
|
185
|
+
_(error.response["error"]).must_equal "RATE_LIMIT_REACHED"
|
|
186
|
+
_(error.message).must_equal "Transloadit Rate Limit Reached. Retry in 0 seconds"
|
|
177
187
|
end
|
|
178
188
|
end
|
|
179
189
|
|
|
@@ -211,6 +221,79 @@ describe Transloadit::Assembly do
|
|
|
211
221
|
end
|
|
212
222
|
end
|
|
213
223
|
|
|
224
|
+
describe "with multiple hash steps" do
|
|
225
|
+
it "must wrap its steps into one hash" do
|
|
226
|
+
hash_1 = {encode: {robot: "/video/encode"}, resize: {robot: "/image/resize"}}
|
|
227
|
+
hash_2 = {thumbs: {robot: "/video/thumbs"}}
|
|
228
|
+
|
|
229
|
+
assembly = Transloadit::Assembly.new @transloadit,
|
|
230
|
+
steps: [hash_1, hash_2]
|
|
231
|
+
(hash_1.keys + hash_2.keys).each do |key|
|
|
232
|
+
_(assembly.to_hash[:steps].keys).must_include key
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
it "must not allow duplicate steps" do
|
|
237
|
+
thumbs = {thumbs: {robot: "/video/thumbs"}}
|
|
238
|
+
thumbs_duplicate = {thumbs: {robot: "/video/encode"}}
|
|
239
|
+
assembly = Transloadit::Assembly.new @transloadit,
|
|
240
|
+
steps: [thumbs, thumbs_duplicate]
|
|
241
|
+
assert_raises ArgumentError do
|
|
242
|
+
assembly.create! open("lib/transloadit/version.rb")
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
describe "with mixture of hashes and steps" do
|
|
248
|
+
it "must wrap its steps into one hash" do
|
|
249
|
+
hash = {encode: {robot: "/video/encode"}, resize: {robot: "/image/resize"}}
|
|
250
|
+
step = @transloadit.step "thumbs", "/video/thumbs"
|
|
251
|
+
|
|
252
|
+
assembly = Transloadit::Assembly.new @transloadit,
|
|
253
|
+
steps: [hash, step]
|
|
254
|
+
|
|
255
|
+
hash.keys.each do |key|
|
|
256
|
+
_(assembly.to_hash[:steps].keys).must_include key
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
_(assembly.to_hash[:steps].keys).must_include step.name
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
it "must not allow duplicate steps" do
|
|
263
|
+
hash = {thumbs: {robot: "/video/thumbs"}}
|
|
264
|
+
step = @transloadit.step "thumbs", "/video/thumbs"
|
|
265
|
+
|
|
266
|
+
assembly = Transloadit::Assembly.new @transloadit,
|
|
267
|
+
steps: [hash, step]
|
|
268
|
+
|
|
269
|
+
assert_raises ArgumentError do
|
|
270
|
+
assembly.create! open("lib/transloadit/version.rb")
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
describe "with unsupported step class" do
|
|
276
|
+
it "raises error" do
|
|
277
|
+
assembly = Transloadit::Assembly.new @transloadit,
|
|
278
|
+
steps: Class.new
|
|
279
|
+
|
|
280
|
+
assert_raises ArgumentError do
|
|
281
|
+
assembly.create! open("lib/transloadit/version.rb")
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
describe "with unsupported array element" do
|
|
287
|
+
it "raises error" do
|
|
288
|
+
assembly = Transloadit::Assembly.new @transloadit,
|
|
289
|
+
steps: [Class.new]
|
|
290
|
+
|
|
291
|
+
assert_raises ArgumentError do
|
|
292
|
+
assembly.create! open("lib/transloadit/version.rb")
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
214
297
|
describe "using assembly API methods" do
|
|
215
298
|
include WebMock::API
|
|
216
299
|
|