braintreehttp 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/braintreehttp.gemspec +3 -1
- data/lib/braintreehttp/encoder.rb +55 -9
- data/lib/braintreehttp/http_client.rb +6 -56
- data/lib/braintreehttp/version.rb +1 -0
- data/spec/braintreehttp/encoder_spec.rb +26 -0
- data/spec/braintreehttp/http_client_spec.rb +7 -74
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 599bde79b1bc33cd0f46c65d37136855ed422343
|
4
|
+
data.tar.gz: 06d2cf5cc5eeda5fad37ba71b07c97f91068331a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81fe0a125d8b991d02e9f3d77171e4b4c18745df78e3f0a6e6c30d01c8edc873952736a047063d45925af236672f2cdc74f2887e175b98c9ebab5d62a7cf87bd
|
7
|
+
data.tar.gz: 9375dd0800ea34885828d90b3c3987cd8571ff312d28af415be69840671210fcd4edd63a06de5cc9758783472cfca22630b29c47b778a60f1d478b49ed93adec
|
data/braintreehttp.gemspec
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
require_relative './lib/braintreehttp/version'
|
2
|
+
|
1
3
|
$:.push File.expand_path("../lib", __FILE__)
|
2
4
|
|
3
5
|
Gem::Specification.new do |s|
|
4
6
|
s.name = "braintreehttp"
|
5
7
|
s.summary = "BraintreeHttp Client Library"
|
6
8
|
s.description = "Used for generated API clients"
|
7
|
-
s.version =
|
9
|
+
s.version = VERSION
|
8
10
|
s.license = "MIT"
|
9
11
|
s.author = "Braintree"
|
10
12
|
s.email = "code@getbraintree.com"
|
@@ -1,10 +1,9 @@
|
|
1
1
|
require 'json'
|
2
2
|
|
3
3
|
module BraintreeHttp
|
4
|
-
|
5
4
|
class Encoder
|
6
5
|
def initialize
|
7
|
-
@encoders = [Json.new, Text.new]
|
6
|
+
@encoders = [Json.new, Text.new, Multipart.new]
|
8
7
|
end
|
9
8
|
|
10
9
|
def serialize_request(req)
|
@@ -16,7 +15,7 @@ module BraintreeHttp
|
|
16
15
|
enc = _encoder(content_type)
|
17
16
|
raise UnsupportedEncodingError.new("Unable to serialize request with Content-Type #{content_type}. Supported encodings are #{supported_encodings}") unless enc
|
18
17
|
|
19
|
-
enc.encode(req
|
18
|
+
enc.encode(req)
|
20
19
|
end
|
21
20
|
|
22
21
|
def deserialize_response(resp, headers)
|
@@ -43,9 +42,8 @@ module BraintreeHttp
|
|
43
42
|
end
|
44
43
|
|
45
44
|
class Json
|
46
|
-
|
47
|
-
|
48
|
-
JSON.generate(body)
|
45
|
+
def encode(request)
|
46
|
+
JSON.generate(request.body)
|
49
47
|
end
|
50
48
|
|
51
49
|
def decode(body)
|
@@ -58,9 +56,8 @@ module BraintreeHttp
|
|
58
56
|
end
|
59
57
|
|
60
58
|
class Text
|
61
|
-
|
62
|
-
|
63
|
-
body.to_s
|
59
|
+
def encode(request)
|
60
|
+
request.body.to_s
|
64
61
|
end
|
65
62
|
|
66
63
|
def decode(body)
|
@@ -71,4 +68,53 @@ module BraintreeHttp
|
|
71
68
|
/^text\/.*/
|
72
69
|
end
|
73
70
|
end
|
71
|
+
|
72
|
+
class Multipart
|
73
|
+
def encode(request)
|
74
|
+
boundary = DateTime.now.strftime("%Q")
|
75
|
+
request.headers["Content-Type"] = "multipart/form-data; boundary=#{boundary}"
|
76
|
+
|
77
|
+
form_params = []
|
78
|
+
request.body.each do |k, v|
|
79
|
+
if v.is_a? File
|
80
|
+
form_params.push(_add_file_part(k, v))
|
81
|
+
else
|
82
|
+
form_params.push(_add_form_field(k, v))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
form_params.collect {|p| "--" + boundary + "#{LINE_FEED}" + p}.join("") + "--" + boundary + "--"
|
87
|
+
end
|
88
|
+
|
89
|
+
def decode(body)
|
90
|
+
raise UnsupportedEncodingError.new("Multipart does not support deserialization")
|
91
|
+
end
|
92
|
+
|
93
|
+
def content_type
|
94
|
+
/^multipart\/.*/
|
95
|
+
end
|
96
|
+
|
97
|
+
def _add_form_field(key, value)
|
98
|
+
return "Content-Disposition: form-data; name=\"#{key}\"#{LINE_FEED}#{LINE_FEED}#{value}#{LINE_FEED}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def _add_file_part(key, file)
|
102
|
+
mime_type = _mime_type_for_file_name(file.path)
|
103
|
+
return "Content-Disposition: form-data; name=\"#{key}\"; filename=\"#{File.basename(file.path)}\"#{LINE_FEED}" +
|
104
|
+
"Content-Type: #{mime_type}#{LINE_FEED}#{LINE_FEED}#{file.read}#{LINE_FEED}"
|
105
|
+
end
|
106
|
+
|
107
|
+
def _mime_type_for_file_name(filename)
|
108
|
+
file_extension = File.extname(filename).strip.downcase[1..-1]
|
109
|
+
if file_extension == "jpeg" || file_extension == "jpg"
|
110
|
+
return "image/jpeg"
|
111
|
+
elsif file_extension == "png"
|
112
|
+
return "image/png"
|
113
|
+
elsif file_extension == "pdf"
|
114
|
+
return "application/pdf"
|
115
|
+
else
|
116
|
+
return "application/octet-stream"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
74
120
|
end
|
@@ -7,7 +7,7 @@ module BraintreeHttp
|
|
7
7
|
LINE_FEED = "\r\n"
|
8
8
|
|
9
9
|
class HttpClient
|
10
|
-
attr_accessor :environment
|
10
|
+
attr_accessor :environment, :encoder
|
11
11
|
|
12
12
|
def initialize(environment)
|
13
13
|
@environment = environment
|
@@ -40,65 +40,15 @@ module BraintreeHttp
|
|
40
40
|
request.headers["User-Agent"] = user_agent
|
41
41
|
end
|
42
42
|
|
43
|
-
|
43
|
+
http_request = Net::HTTPGenericRequest.new(request.verb, true, true, request.path, request.headers)
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
boundary = DateTime.now.strftime("%Q")
|
48
|
-
httpRequest.set_content_type("multipart/form-data; boundary=#{boundary}")
|
49
|
-
|
50
|
-
form_params = []
|
51
|
-
request.body.each do |k, v|
|
52
|
-
if v.is_a? File
|
53
|
-
form_params.push(_add_file_part(k, v))
|
54
|
-
else
|
55
|
-
form_params.push(_add_form_field(k, v))
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
httpRequest.body = form_params.collect {|p| "--" + boundary + "#{LINE_FEED}" + p}.join("") + "--" + boundary + "--"
|
60
|
-
elsif has_body(request)
|
61
|
-
if request.body.is_a? String
|
62
|
-
httpRequest.body = request.body
|
63
|
-
else
|
64
|
-
httpRequest.body = serialize_request(request)
|
65
|
-
end
|
45
|
+
if has_body(request)
|
46
|
+
http_request.body = @encoder.serialize_request(request)
|
66
47
|
end
|
67
48
|
|
68
49
|
uri = URI(@environment.base_url)
|
69
50
|
Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
|
70
|
-
_parse_response(http.request(
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def serialize_request(request)
|
75
|
-
@encoder.serialize_request(request)
|
76
|
-
end
|
77
|
-
|
78
|
-
def deserialize_response(response_body, headers)
|
79
|
-
@encoder.deserialize_response(response_body, headers)
|
80
|
-
end
|
81
|
-
|
82
|
-
def _add_form_field(key, value)
|
83
|
-
return "Content-Disposition: form-data; name=\"#{key}\"#{LINE_FEED}#{LINE_FEED}#{value}#{LINE_FEED}"
|
84
|
-
end
|
85
|
-
|
86
|
-
def _add_file_part(key, file)
|
87
|
-
mime_type = _mime_type_for_file_name(file.path)
|
88
|
-
return "Content-Disposition: form-data; name=\"#{key}\"; filename=\"#{File.basename(file.path)}\"#{LINE_FEED}" +
|
89
|
-
"Content-Type: #{mime_type}#{LINE_FEED}#{LINE_FEED}#{file.read}#{LINE_FEED}"
|
90
|
-
end
|
91
|
-
|
92
|
-
def _mime_type_for_file_name(filename)
|
93
|
-
file_extension = File.extname(filename).strip.downcase[1..-1]
|
94
|
-
if file_extension == "jpeg" || file_extension == "jpg"
|
95
|
-
return "image/jpeg"
|
96
|
-
elsif file_extension == "png"
|
97
|
-
return "image/png"
|
98
|
-
elsif file_extension == "pdf"
|
99
|
-
return "application/pdf"
|
100
|
-
else
|
101
|
-
return "application/octet-stream"
|
51
|
+
_parse_response(http.request(http_request))
|
102
52
|
end
|
103
53
|
end
|
104
54
|
|
@@ -108,7 +58,7 @@ module BraintreeHttp
|
|
108
58
|
result = response.body
|
109
59
|
headers = response.to_hash
|
110
60
|
if result && !result.empty?
|
111
|
-
deserialized = deserialize_response(response.body, headers)
|
61
|
+
deserialized = @encoder.deserialize_response(response.body, headers)
|
112
62
|
if deserialized.is_a?(String) || deserialized.is_a?(Array)
|
113
63
|
result = deserialized
|
114
64
|
else
|
@@ -0,0 +1 @@
|
|
1
|
+
VERSION = "0.2.0"
|
@@ -36,6 +36,32 @@ describe Encoder do
|
|
36
36
|
expect(Encoder.new.serialize_request(req)).to eq("some text")
|
37
37
|
end
|
38
38
|
|
39
|
+
it 'serializes the request when content-type == multipart/form-data' do
|
40
|
+
http_client = HttpClient.new(@environment)
|
41
|
+
file = File.new("README.md", "r")
|
42
|
+
req = OpenStruct.new({
|
43
|
+
:verb => "POST",
|
44
|
+
:path => "/v1/api",
|
45
|
+
:headers => {
|
46
|
+
"Content-Type" => "multipart/form-data"
|
47
|
+
},
|
48
|
+
:body => {
|
49
|
+
:key => "value",
|
50
|
+
:another_key => 1013,
|
51
|
+
:readme => file,
|
52
|
+
}
|
53
|
+
})
|
54
|
+
|
55
|
+
serialized = Encoder.new.serialize_request(req)
|
56
|
+
|
57
|
+
expect(serialized).to include("Content-Disposition: form-data; name=\"readme\"; filename=\"README.md\"")
|
58
|
+
expect(serialized).to include("Content-Disposition: form-data; name=\"readme\"; filename=\"README.md\"")
|
59
|
+
expect(serialized).to include("Content-Disposition: form-data; name=\"key\"")
|
60
|
+
expect(serialized).to include("value")
|
61
|
+
expect(serialized).to include("Content-Disposition: form-data; name=\"another_key\"")
|
62
|
+
expect(serialized).to include("1013")
|
63
|
+
end
|
64
|
+
|
39
65
|
it 'throws when content-type is not application/json' do
|
40
66
|
req = OpenStruct.new({
|
41
67
|
:headers => {
|
@@ -58,7 +58,13 @@ describe HttpClient do
|
|
58
58
|
|
59
59
|
stub_request(:delete, @environment.base_url + "/path")
|
60
60
|
|
61
|
-
req = OpenStruct.new({
|
61
|
+
req = OpenStruct.new({
|
62
|
+
:verb => "DELETE",
|
63
|
+
:path => "/path",
|
64
|
+
:headers => {
|
65
|
+
"Content-Type" => "text/plain"
|
66
|
+
}
|
67
|
+
})
|
62
68
|
|
63
69
|
req.body = "I want to delete the thing"
|
64
70
|
|
@@ -157,36 +163,6 @@ describe HttpClient do
|
|
157
163
|
expect(resp.result.key).to eq('value')
|
158
164
|
end
|
159
165
|
|
160
|
-
it "allows subclasses to modify response body" do
|
161
|
-
WebMock.enable!
|
162
|
-
|
163
|
-
return_data = {
|
164
|
-
:key => "value"
|
165
|
-
}
|
166
|
-
|
167
|
-
class JSONHttpClient < HttpClient
|
168
|
-
def deserialize_response(body, headers)
|
169
|
-
if headers["content-type"].include? "application/json"
|
170
|
-
return 'something else'
|
171
|
-
end
|
172
|
-
|
173
|
-
body
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
http_client = JSONHttpClient.new(@environment)
|
178
|
-
|
179
|
-
stub_request(:get, @environment.base_url + "/v1/api")
|
180
|
-
.to_return(body: JSON.generate(return_data), status: 200, headers: {"Content-Type" => "application/json"})
|
181
|
-
|
182
|
-
req = OpenStruct.new({:verb => "GET", :path => "/v1/api"})
|
183
|
-
|
184
|
-
resp = http_client.execute(req)
|
185
|
-
|
186
|
-
expect(resp.status_code).to eq(200)
|
187
|
-
expect(resp.result).to eq('something else')
|
188
|
-
end
|
189
|
-
|
190
166
|
it 'handles json array result' do
|
191
167
|
WebMock.enable!
|
192
168
|
|
@@ -204,49 +180,6 @@ describe HttpClient do
|
|
204
180
|
expect(resp.result).to eq(return_data)
|
205
181
|
end
|
206
182
|
|
207
|
-
it "encodes multipart/form-data when a file is present without body" do
|
208
|
-
WebMock.enable!
|
209
|
-
|
210
|
-
stub_request(:any, @environment.base_url + "/v1/api")
|
211
|
-
|
212
|
-
http_client = HttpClient.new(@environment)
|
213
|
-
file = File.new("README.md", "r")
|
214
|
-
req = OpenStruct.new({:verb => "POST", :path => "/v1/api", :headers => {"Content-Type" => "multipart/form-data"}})
|
215
|
-
req.body = {:readme => file}
|
216
|
-
|
217
|
-
resp = http_client.execute(req)
|
218
|
-
|
219
|
-
assert_requested(:post, @environment.base_url + "/v1/api") { |requested|
|
220
|
-
requested.body.include? "Content-Disposition: form-data; name=\"readme\"; filename=\"README.md\""
|
221
|
-
}
|
222
|
-
end
|
223
|
-
|
224
|
-
it "encodes multipart/form-data when a file is present with body" do
|
225
|
-
WebMock.enable!
|
226
|
-
|
227
|
-
stub_request(:any, @environment.base_url + "/v1/api")
|
228
|
-
|
229
|
-
http_client = HttpClient.new(@environment)
|
230
|
-
file = File.new("README.md", "r")
|
231
|
-
|
232
|
-
req = OpenStruct.new({:verb => "POST", :path => "/v1/api", :headers => {"Content-Type" => "multipart/form-data"}})
|
233
|
-
req.body = {
|
234
|
-
:key => "value",
|
235
|
-
:another_key => 1013,
|
236
|
-
:readme => file,
|
237
|
-
}
|
238
|
-
|
239
|
-
resp = http_client.execute(req)
|
240
|
-
|
241
|
-
assert_requested(:post, @environment.base_url + "/v1/api") { |requested|
|
242
|
-
requested.body.include? "Content-Disposition: form-data; name=\"readme\"; filename=\"README.md\""
|
243
|
-
requested.body.include? "Content-Disposition: form-data; name=\"key\""
|
244
|
-
requested.body.include? "value"
|
245
|
-
requested.body.include? "Content-Disposition: form-data; name=\"another_key\""
|
246
|
-
requested.body.include? "1013"
|
247
|
-
}
|
248
|
-
end
|
249
|
-
|
250
183
|
it "does not error if no file or body present on a request class" do
|
251
184
|
class Request
|
252
185
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: braintreehttp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Braintree
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Used for generated API clients
|
14
14
|
email: code@getbraintree.com
|
@@ -22,6 +22,7 @@ files:
|
|
22
22
|
- lib/braintreehttp/environment.rb
|
23
23
|
- lib/braintreehttp/errors.rb
|
24
24
|
- lib/braintreehttp/http_client.rb
|
25
|
+
- lib/braintreehttp/version.rb
|
25
26
|
- spec/braintreehttp/encoder_spec.rb
|
26
27
|
- spec/braintreehttp/http_client_spec.rb
|
27
28
|
- spec/spec_helper.rb
|
@@ -45,7 +46,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
46
|
version: '0'
|
46
47
|
requirements: []
|
47
48
|
rubyforge_project: braintreehttp
|
48
|
-
rubygems_version: 2.
|
49
|
+
rubygems_version: 2.2.2
|
49
50
|
signing_key:
|
50
51
|
specification_version: 4
|
51
52
|
summary: BraintreeHttp Client Library
|