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.
@@ -1,6 +1,8 @@
1
1
  require "transloadit"
2
2
 
3
- require "rest-client"
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[url.path + to_query(params)].get(API_HEADERS)
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
- options = {payload: to_payload(payload)}
64
- api(options)[url.path].delete(API_HEADERS)
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[url.path].post(to_payload(payload), API_HEADERS)
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[url.path].put(to_payload(payload), API_HEADERS)
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 [RestClient::Resource] the API endpoint for this instance
125
+ # @return [Faraday::Connection] the API endpoint for this instance
122
126
  #
123
- def api(options = {})
127
+ def api
124
128
  @api ||= case url.host
125
- when String then RestClient::Resource.new("#{url.scheme}://#{url.host}", options)
126
- else RestClient::Resource.new("#{API_ENDPOINT.scheme}://#{API_ENDPOINT.host}", options)
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
- # Copy all values, excluding :params and :signature keys.
155
- new_payload.update payload.reject { |key, _| key == :params || key == :signature }
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 results in a Transloadit::Response, even if an exception
185
- # is raised by RestClient.
217
+ # Wraps a request's result in a Transloadit::Response.
186
218
  #
187
219
  def request!(&request)
188
- Transloadit::Response.new yield
189
- rescue RestClient::Exception => e
190
- # The response attribute can be nil, for example for RestClient::Exceptions::OpenTimeout exceptions.
191
- # Then, we cannot convert them into a Transloadit::Response, so instead we raise them again for
192
- # the user to be visible.
193
- # See https://github.com/transloadit/ruby-sdk/issues/53
194
- raise e if e.response.nil?
195
- Transloadit::Response.new e.response
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
  #
@@ -1,20 +1,30 @@
1
1
  require "transloadit"
2
2
 
3
- require "rest-client"
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 an enhanced response wrapped around a RestClient response.
7
+ # Creates a response without exposing the underlying HTTP client.
11
8
  #
12
- # @param [RestClient::Response] response the JSON response to wrap
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(response)
15
- __setobj__(response)
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 __getobj__.body
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
- # Sets the object to delegate method calls to.
70
+ # Replaces this response's HTTP data with another response's data.
72
71
  #
73
- # @param [RestClient::Response] response the response to delegate to
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
- __setobj__ other.__getobj__
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
@@ -1,3 +1,3 @@
1
1
  class Transloadit
2
- VERSION = "3.1.2"
2
+ VERSION = "4.1.0"
3
3
  end
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.stub :now, fixed_time do
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
- Transloadit::Assembly.new(
68
- transloadit
69
- ).create! open("lib/transloadit/version.rb")
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["tag"]).must_equal "ninja-cat"
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
- mocker = Minitest::Mock.new
150
- mocker.expect :call, nil, [file]
151
- @assembly.stub :create!, mocker do
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
- mocker.verify
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