paypalhttp 1.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6e1d7486774ce1d1a9a52d214a00f3212196d0d1da58b599861056a0367753db
4
+ data.tar.gz: 18f0971f48707107899ed7cbd397384605b334e761ef33176c9b642742e8bf31
5
+ SHA512:
6
+ metadata.gz: 219e4ffe88eeabfde09731075a2dfd92394568c14d64c0ac4d6f9ac2d2cfe7e24e36353150555fe922077b761e5b01678ae1d3df0435cb10a377bae99d223870
7
+ data.tar.gz: ed6ee01c152150c9df2d34b47cb1f620271e549c1f8e8953cdbc19eded34d83c7604c97ca6929db8ab9feb972ea03d8221c62758151a615de65b73ac915171e0
@@ -0,0 +1,7 @@
1
+ module PayPalHttp
2
+ require_relative "paypalhttp/environment"
3
+ require_relative "paypalhttp/http_client"
4
+ require_relative "paypalhttp/errors"
5
+ require_relative "paypalhttp/encoder"
6
+ require_relative "paypalhttp/serializers/form_part"
7
+ end
@@ -0,0 +1,77 @@
1
+ require 'stringio'
2
+ require 'zlib'
3
+
4
+ require_relative './serializers/json'
5
+ require_relative './serializers/form_encoded'
6
+ require_relative './serializers/text'
7
+ require_relative './serializers/multipart'
8
+
9
+ module PayPalHttp
10
+ class Encoder
11
+ def initialize
12
+ @encoders = [Json.new, Text.new, Multipart.new, FormEncoded.new]
13
+ end
14
+
15
+ def serialize_request(req)
16
+ raise UnsupportedEncodingError.new('HttpRequest did not have Content-Type header set') unless req.headers && (req.headers['content-type'])
17
+
18
+ content_type = _extract_header(req.headers, 'content-type')
19
+
20
+ enc = _encoder(content_type)
21
+ raise UnsupportedEncodingError.new("Unable to serialize request with Content-Type #{content_type}. Supported encodings are #{supported_encodings}") unless enc
22
+
23
+ encoded = enc.encode(req)
24
+ content_encoding = _extract_header(req.headers, 'content-encoding')
25
+
26
+ if content_encoding == 'gzip'
27
+ out = StringIO.new('w')
28
+ writer = Zlib::GzipWriter.new(out)
29
+
30
+ writer.write encoded
31
+
32
+ writer.close
33
+
34
+ encoded = out.string
35
+ end
36
+
37
+ encoded
38
+ end
39
+
40
+ def deserialize_response(resp, headers)
41
+ raise UnsupportedEncodingError.new('HttpResponse did not have Content-Type header set') unless headers && (headers['content-type'])
42
+
43
+ content_type = _extract_header(headers, 'content-type')
44
+
45
+ enc = _encoder(content_type)
46
+ raise UnsupportedEncodingError.new("Unable to deserialize response with Content-Type #{content_type}. Supported decodings are #{supported_encodings}") unless enc
47
+
48
+ content_encoding = _extract_header(headers, 'content-encoding')
49
+
50
+ if content_encoding == 'gzip'
51
+ buf = StringIO.new(resp, 'rb')
52
+ reader = Zlib::GzipReader.new(buf)
53
+
54
+ resp = reader.read
55
+ end
56
+
57
+ enc.decode(resp)
58
+ end
59
+
60
+ def supported_encodings
61
+ @encoders.map { |enc| enc.content_type.inspect }
62
+ end
63
+
64
+ def _encoder(content_type)
65
+ idx = @encoders.index { |enc| enc.content_type.match(content_type) }
66
+
67
+ @encoders[idx] if idx
68
+ end
69
+
70
+ def _extract_header(headers, key)
71
+ value = headers[key.downcase]
72
+ value = value.first if value.kind_of?(Array)
73
+
74
+ value
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,9 @@
1
+ module PayPalHttp
2
+ class Environment
3
+ attr_accessor :base_url
4
+ def initialize(base_url)
5
+ @base_url = base_url
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,16 @@
1
+ module PayPalHttp
2
+ class HttpError < IOError
3
+ attr_accessor :status_code, :result, :headers
4
+ def initialize(status_code, result, headers)
5
+ @status_code = status_code
6
+ @result = result
7
+ @headers = headers
8
+ end
9
+ end
10
+
11
+ class UnsupportedEncodingError < IOError
12
+ def initialize(msg)
13
+ super(msg)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,134 @@
1
+ require 'ostruct'
2
+ require 'net/http'
3
+ require 'date'
4
+
5
+ module PayPalHttp
6
+
7
+ class HttpClient
8
+ attr_accessor :environment, :encoder
9
+
10
+ def initialize(environment)
11
+ @environment = environment
12
+ @injectors = []
13
+ @encoder = Encoder.new
14
+ end
15
+
16
+ def user_agent
17
+ "PayPalHttp-Ruby HTTP/1.1"
18
+ end
19
+
20
+ def add_injector(&block)
21
+ @injectors << block
22
+ end
23
+
24
+ def has_body(request)
25
+ request.respond_to?(:body) and request.body
26
+ end
27
+
28
+ def format_headers(headers)
29
+ formatted_headers = {}
30
+ headers.each do |key, value|
31
+ formatted_headers[key.downcase] = value
32
+ end
33
+ formatted_headers
34
+ end
35
+
36
+ def map_headers(raw_headers , formatted_headers)
37
+ raw_headers.each do |key, value|
38
+ if formatted_headers.key?(key.downcase) == true
39
+ raw_headers[key] = formatted_headers[key.downcase]
40
+ end
41
+ end
42
+ raw_headers
43
+ end
44
+
45
+ def execute(req)
46
+ headers = req.headers || {}
47
+
48
+ request = OpenStruct.new({
49
+ :verb => req.verb,
50
+ :path => req.path,
51
+ :headers => headers.clone,
52
+ :body => req.body,
53
+ })
54
+
55
+ if !request.headers
56
+ request.headers = {}
57
+ end
58
+
59
+ @injectors.each do |injector|
60
+ injector.call(request)
61
+ end
62
+
63
+ formatted_headers = format_headers(request.headers)
64
+ if !formatted_headers["user-agent"] || formatted_headers["user-agent"] == "Ruby"
65
+ request.headers["user-agent"] = user_agent
66
+ end
67
+
68
+ body = nil
69
+ if has_body(request)
70
+ raw_headers = request.headers
71
+ request.headers = formatted_headers
72
+ body = @encoder.serialize_request(request)
73
+ request.headers = map_headers(raw_headers, request.headers)
74
+ end
75
+
76
+ http_request = Net::HTTPGenericRequest.new(request.verb, body != nil, true, request.path, request.headers)
77
+ http_request.body = body
78
+
79
+ uri = URI(@environment.base_url)
80
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
81
+ _parse_response(http.request(http_request))
82
+ end
83
+ end
84
+
85
+ def _parse_response(response)
86
+ status_code = response.code.to_i
87
+
88
+ result = response.body
89
+ headers = response.to_hash
90
+ if result && !result.empty?
91
+ deserialized = @encoder.deserialize_response(response.body, format_headers(headers))
92
+ if deserialized.is_a?(String) || deserialized.is_a?(Array)
93
+ result = deserialized
94
+ else
95
+ result = _parse_values(deserialized)
96
+ end
97
+ else
98
+ result = nil
99
+ end
100
+
101
+ obj = OpenStruct.new({
102
+ :status_code => status_code,
103
+ :result => result,
104
+ :headers => response.to_hash,
105
+ })
106
+
107
+ if status_code >= 200 and status_code < 300
108
+ return obj
109
+ elsif
110
+ raise HttpError.new(obj.status_code, obj.result, obj.headers)
111
+ end
112
+ end
113
+
114
+ def _parse_values(values)
115
+ obj = nil
116
+
117
+ if values.is_a?(Array)
118
+ obj = []
119
+ values.each do |v|
120
+ obj << _parse_values(v)
121
+ end
122
+ elsif values.is_a?(Hash)
123
+ obj = OpenStruct.new()
124
+ values.each do |k, v|
125
+ obj[k] = _parse_values(v)
126
+ end
127
+ else
128
+ obj = values
129
+ end
130
+
131
+ obj
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,22 @@
1
+ require 'uri'
2
+
3
+ module PayPalHttp
4
+ class FormEncoded
5
+ def encode(request)
6
+ encoded_params = []
7
+ request.body.each do |k, v|
8
+ encoded_params.push("#{URI.escape(k.to_s)}=#{URI.escape(v.to_s)}")
9
+ end
10
+
11
+ encoded_params.join("&")
12
+ end
13
+
14
+ def decode(body)
15
+ raise UnsupportedEncodingError.new("FormEncoded does not support deserialization")
16
+ end
17
+
18
+ def content_type
19
+ /^application\/x-www-form-urlencoded/
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ module PayPalHttp
2
+ class FormPart
3
+ attr_accessor :value, :headers
4
+
5
+ def initialize(value, headers)
6
+ @value = value
7
+ @headers = {}
8
+
9
+ headers.each do |key, value|
10
+ @headers[key.to_s.downcase.split('-').map { |word| "#{word[0].upcase}#{word[1..-1]}" }.join("-")] = value
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+
3
+ module PayPalHttp
4
+ class Json
5
+ def encode(request)
6
+ JSON.generate(request.body)
7
+ end
8
+
9
+ def decode(body)
10
+ JSON.parse(body)
11
+ end
12
+
13
+ def content_type
14
+ /application\/json/
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,99 @@
1
+ module PayPalHttp
2
+ class Multipart
3
+
4
+ LINE_FEED = "\r\n"
5
+
6
+ def encode(request)
7
+ boundary = DateTime.now.strftime("%Q")
8
+ request.headers["content-type"] = "#{request.headers['content-type']}; boundary=#{boundary}"
9
+
10
+ form_params = []
11
+ value_params = []
12
+ file_params = []
13
+
14
+ request.body.each do |k, v|
15
+ if v.is_a? File
16
+ file_params.push(_add_file_part(k, v))
17
+ elsif v.is_a? FormPart
18
+ value_params.push(_add_form_part(k, v))
19
+ else
20
+ value_params.push(_add_form_field(k, v))
21
+ end
22
+ end
23
+
24
+ form_params = value_params + file_params
25
+ form_params.collect {|p| "--" + boundary + "#{LINE_FEED}" + p}.join("") + "--" + boundary + "--"
26
+ end
27
+
28
+ def decode(body)
29
+ raise UnsupportedEncodingError.new("Multipart does not support deserialization")
30
+ end
31
+
32
+ def content_type
33
+ /multipart\/.*/
34
+ end
35
+
36
+ def _add_form_field(key, value)
37
+ return "Content-Disposition: form-data; name=\"#{key}\"#{LINE_FEED}#{LINE_FEED}#{value}#{LINE_FEED}"
38
+ end
39
+
40
+ def _add_form_part(key, form_part)
41
+ retValue = "Content-Disposition: form-data; name=\"#{key}\""
42
+ formatted_headers = format_headers(form_part.headers)
43
+ if formatted_headers["content-type"] == "application/json"
44
+ retValue += "; filename=\"#{key}.json\""
45
+ end
46
+ retValue += "#{LINE_FEED}"
47
+
48
+ form_part.headers.each do |key, value|
49
+ retValue += "#{key}: #{value}#{LINE_FEED}"
50
+ end
51
+
52
+ retValue += "#{LINE_FEED}"
53
+
54
+ if formatted_headers["content-type"]
55
+ retValue += Encoder.new().serialize_request(OpenStruct.new({
56
+ :verb => 'POST',
57
+ :path => '/',
58
+ :headers => formatted_headers,
59
+ :body => form_part.value
60
+ }))
61
+ else
62
+ retValue += form_part.value
63
+ end
64
+
65
+ retValue += "#{LINE_FEED}"
66
+
67
+ return retValue
68
+ end
69
+
70
+ def _add_file_part(key, file)
71
+ mime_type = _mime_type_for_file_name(file.path)
72
+ return "Content-Disposition: form-data; name=\"#{key}\"; filename=\"#{File.basename(file.path)}\"#{LINE_FEED}" +
73
+ "Content-Type: #{mime_type}#{LINE_FEED}#{LINE_FEED}#{file.read}#{LINE_FEED}"
74
+ end
75
+
76
+ def _mime_type_for_file_name(filename)
77
+ file_extension = File.extname(filename).strip.downcase[1..-1]
78
+ if file_extension == "jpeg" || file_extension == "jpg"
79
+ return "image/jpeg"
80
+ elsif file_extension == "gif"
81
+ return "image/gif"
82
+ elsif file_extension == "png"
83
+ return "image/png"
84
+ elsif file_extension == "pdf"
85
+ return "application/pdf"
86
+ else
87
+ return "application/octet-stream"
88
+ end
89
+ end
90
+
91
+ def format_headers(headers)
92
+ formatted_headers = {}
93
+ headers.each do |key, value|
94
+ formatted_headers[key.downcase] = value
95
+ end
96
+ formatted_headers
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,15 @@
1
+ module PayPalHttp
2
+ class Text
3
+ def encode(request)
4
+ request.body.to_s
5
+ end
6
+
7
+ def decode(body)
8
+ body.to_s
9
+ end
10
+
11
+ def content_type
12
+ /text\/.*/
13
+ end
14
+ end
15
+ end
@@ -0,0 +1 @@
1
+ VERSION = "1.0.0"
@@ -0,0 +1,15 @@
1
+ require_relative './lib/paypalhttp/version'
2
+
3
+ $:.push File.expand_path("../lib", __FILE__)
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "paypalhttp"
7
+ s.summary = "PayPalHttp Client Library"
8
+ s.description = "Used for generated API clients"
9
+ s.version = VERSION
10
+ s.license = "MIT"
11
+ s.author = "PayPal"
12
+ s.rubyforge_project = "paypalhttp"
13
+ s.has_rdoc = false
14
+ s.files = Dir.glob ["lib/**/*.{rb}", "spec/**/*", "*.gemspec"]
15
+ end
@@ -0,0 +1,247 @@
1
+ require 'ostruct'
2
+ require 'json'
3
+ require 'stringio'
4
+ require 'zlib'
5
+
6
+ describe Encoder do
7
+
8
+ describe 'serialize_request' do
9
+ it 'serializes the request when content-type == application/json' do
10
+ req = OpenStruct.new({
11
+ :headers => {
12
+ "content-type" => "application/json; charset=utf8"
13
+ },
14
+ :body => {
15
+ "string" => "value",
16
+ "number" => 1.23,
17
+ "bool" => true,
18
+ "array" => ["one", "two", "three"],
19
+ "nested" => {
20
+ "nested_string" => "nested_value",
21
+ "nested_array" => [1,2,3]
22
+ }
23
+ }
24
+ })
25
+
26
+ expected = '{"string":"value","number":1.23,"bool":true,"array":["one","two","three"],"nested":{"nested_string":"nested_value","nested_array":[1,2,3]}}'
27
+
28
+ expect(Encoder.new.serialize_request(req)).to eq(expected)
29
+ end
30
+
31
+ it 'serializes the request when content-type == text/*' do
32
+ req = OpenStruct.new({
33
+ :headers => {
34
+ "content-type" => "text/plain; charset=utf8"
35
+ },
36
+ :body => "some text"
37
+ })
38
+
39
+ expect(Encoder.new.serialize_request(req)).to eq("some text")
40
+ end
41
+
42
+ it 'serializes the request when content-type == multipart/form-data' do
43
+ file = File.new("README.md", "r")
44
+ req = OpenStruct.new({
45
+ :verb => "POST",
46
+ :path => "/v1/api",
47
+ :headers => {
48
+ "content-type" => "multipart/form-data; charset=utf8"
49
+ },
50
+ :body => {
51
+ :key => "value",
52
+ :another_key => 1013,
53
+ :readme => file
54
+ }
55
+ })
56
+
57
+ serialized = Encoder.new.serialize_request(req)
58
+
59
+ expect(req.headers['content-type']).to include('multipart/form-data; charset=utf8; boundary=')
60
+
61
+ expect(serialized).to include("Content-Disposition: form-data; name=\"readme\"; filename=\"README.md\"")
62
+ expect(serialized).to include("Content-Disposition: form-data; name=\"key\"")
63
+ expect(serialized).to include("value")
64
+ expect(serialized).to include("Content-Disposition: form-data; name=\"another_key\"")
65
+ expect(serialized).to include("1013")
66
+ end
67
+
68
+ it 'serializes the request when Json is provided inside multipart/form-data' do
69
+ file = File.new("README.md", "r")
70
+ req = OpenStruct.new({
71
+ :verb => "POST",
72
+ :path => "/v1/api",
73
+ :headers => {
74
+ "content-type" => "multipart/form-data; charset=utf8"
75
+ },
76
+ :body => {
77
+ :readme => file,
78
+ :input=> FormPart.new({:key => 'val'}, {'content-type': 'application/json'}),
79
+ }
80
+ })
81
+
82
+ serialized = Encoder.new.serialize_request(req)
83
+
84
+ expect(req.headers['content-type']).to include('multipart/form-data; charset=utf8; boundary=')
85
+
86
+ expect(serialized).to include("Content-Disposition: form-data; name=\"readme\"; filename=\"README.md\"")
87
+ expect(serialized).to include("Content-Type: application/octet-stream")
88
+ expect(serialized).to include("Content-Disposition: form-data; name=\"input\"; filename=\"input.json\"")
89
+ expect(serialized).to include("Content-Type: application/json")
90
+ expect(serialized).to include("{\"key\":\"val\"}")
91
+ expect(serialized).to match(/.*Content-Disposition: form-data; name=\"input\"; filename=\"input.json\".*Content-Disposition: form-data; name=\"readme\"; filename=\"README.md\".*/m)
92
+ end
93
+
94
+ it 'serializes the request when content-type == application/x-www-form-urlencoded' do
95
+ req = OpenStruct.new({
96
+ :verb => "POST",
97
+ :path => "/v1/api",
98
+ :headers => {
99
+ "content-type" => "application/x-www-form-urlencoded; charset=utf8"
100
+ },
101
+ :body => {
102
+ :key => "value with a space",
103
+ :another_key => 1013,
104
+ }
105
+ })
106
+ serialized = Encoder.new.serialize_request(req)
107
+
108
+ expect(serialized).to eq("key=value%20with%20a%20space&another_key=1013")
109
+ end
110
+
111
+ it 'throws when content-type is unsupported' do
112
+ req = OpenStruct.new({
113
+ :headers => {
114
+ "content-type" => "fake/content-type"
115
+ },
116
+ :body => { :string => "value" }
117
+ })
118
+
119
+ expect{Encoder.new.serialize_request(req)}.to raise_error(UnsupportedEncodingError, /Unable to serialize request with Content-Type .*\. Supported encodings are .*/)
120
+ end
121
+
122
+ it 'throws when headers undefined' do
123
+ req = OpenStruct.new({
124
+ :body => { :string => "value" }
125
+ })
126
+
127
+ expect{Encoder.new.serialize_request(req)}.to raise_error(UnsupportedEncodingError, 'HttpRequest did not have Content-Type header set')
128
+ end
129
+
130
+ it 'throws when conent-type undefined' do
131
+ req = OpenStruct.new({
132
+ :headers => {},
133
+ :body => { :key => "value" }
134
+ })
135
+
136
+
137
+ expect{Encoder.new.serialize_request(req)}.to raise_error(UnsupportedEncodingError, 'HttpRequest did not have Content-Type header set')
138
+ end
139
+
140
+ it 'encodes data as gzip when content-encoding == gzip' do
141
+ req = OpenStruct.new({
142
+ :path => '/143j2bz1',
143
+ :verb => "POST",
144
+ :headers => {
145
+ 'content-type' => 'application/json',
146
+ 'content-encoding' => 'gzip'
147
+ },
148
+ :body => {
149
+ :one => "two"
150
+ }
151
+ })
152
+
153
+ encoder = Encoder.new
154
+
155
+ out = StringIO.new('w')
156
+ writer = Zlib::GzipWriter.new(out)
157
+ writer.write JSON.generate(req.body)
158
+ writer.close
159
+
160
+ expected_body = out.string
161
+
162
+ expect(encoder.serialize_request(req)).to eq(expected_body)
163
+ end
164
+ end
165
+
166
+ describe 'deserialize_response' do
167
+ it 'throws when content-type unsupported' do
168
+ body = '{"string":"value","number":1.23,"bool":true,"array":["one","two","three"],"nested":{"nested_string":"nested_value","nested_array":[1,2,3]}}'
169
+ headers = {
170
+ "content-type" => ["application/xml"]
171
+ }
172
+
173
+ expect{Encoder.new.deserialize_response(body, headers)}.to raise_error(UnsupportedEncodingError, /Unable to deserialize response with Content-Type .*\. Supported decodings are .*/)
174
+ end
175
+
176
+ it 'throws when headers undefined' do
177
+ body = '{"string":"value","number":1.23,"bool":true,"array":["one","two","three"],"nested":{"nested_string":"nested_value","nested_array":[1,2,3]}}'
178
+
179
+ expect{Encoder.new.deserialize_response(body, nil)}.to raise_error(UnsupportedEncodingError, 'HttpResponse did not have Content-Type header set')
180
+ end
181
+
182
+ it 'throws when content-type undefined' do
183
+ body = '{"string":"value","number":1.23,"bool":true,"array":["one","two","three"],"nested":{"nested_string":"nested_value","nested_array":[1,2,3]}}'
184
+
185
+ expect{Encoder.new.deserialize_response(body, {})}.to raise_error(UnsupportedEncodingError, 'HttpResponse did not have Content-Type header set')
186
+ end
187
+
188
+ it 'deserializes the response when content-type == application/json' do
189
+ expected = {
190
+ "string" => "value",
191
+ "number" => 1.23,
192
+ "bool" => true,
193
+ "array" => ["one", "two", "three"],
194
+ "nested" => {
195
+ "nested_string" => "nested_value",
196
+ "nested_array" => [1,2,3]
197
+ }
198
+ }
199
+
200
+ headers = {"content-type" => ["application/json; charset=utf8"]}
201
+ body = '{"string":"value","number":1.23,"bool":true,"array":["one","two","three"],"nested":{"nested_string":"nested_value","nested_array":[1,2,3]}}'
202
+
203
+ deserialized = Encoder.new.deserialize_response(body, headers)
204
+
205
+ expect(deserialized).to eq(expected)
206
+ end
207
+
208
+ it 'deserializes the response when content-type == text/*' do
209
+ headers = {"content-type" => ["text/plain; charset=utf8"]}
210
+ body = 'some text'
211
+
212
+ expect(Encoder.new.deserialize_response(body, headers)).to eq('some text')
213
+ end
214
+
215
+ it 'throws when attempting to deserialize multipart/*' do
216
+ headers = {"content-type" => ["multipart/form-data"]}
217
+ body = 'some multipart encoded data here'
218
+
219
+ expect{Encoder.new.deserialize_response(body, headers)}.to raise_error(UnsupportedEncodingError, 'Multipart does not support deserialization')
220
+ end
221
+
222
+ it 'throws when attempting to deserialize application/x-www-form-urlencoded' do
223
+ headers = {"content-type" => ["application/x-www-form-urlencoded"]}
224
+ body = 'some multipart encoded data here'
225
+
226
+ expect{Encoder.new.deserialize_response(body, headers)}.to raise_error(UnsupportedEncodingError, 'FormEncoded does not support deserialization')
227
+ end
228
+
229
+ it 'decodes data from gzip when content-encoding == gzip' do
230
+ headers = {
231
+ 'content-type' => 'application/json',
232
+ 'content-encoding' => 'gzip'
233
+ }
234
+ body = JSON.generate({
235
+ :one => 'two'
236
+ })
237
+
238
+ out = StringIO.new('w')
239
+ encoder = Zlib::GzipWriter.new(out)
240
+ encoder.write(body)
241
+ encoder.close
242
+ encoded_body = out.string
243
+
244
+ expect(Encoder.new.deserialize_response(encoded_body, headers)).to eq({'one' => 'two'})
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,284 @@
1
+ require 'net/http'
2
+ require 'ostruct'
3
+
4
+ describe HttpClient do
5
+
6
+ before do
7
+ WebMock.disable!
8
+ @environment = Environment.new('https://ip.jsontest.com')
9
+ end
10
+
11
+ it "uses injectors to modify request" do
12
+ WebMock.enable!
13
+
14
+ http_client = HttpClient.new(@environment)
15
+
16
+ http_client.add_injector do |request|
17
+ request.headers["Some-Key"] = "Some Value"
18
+ end
19
+
20
+ req = OpenStruct.new({:verb => "GET", :path => "/"})
21
+
22
+ stub_request(:any, @environment.base_url)
23
+
24
+ http_client.execute(req)
25
+
26
+ assert_requested :get, "#{@environment.base_url}/", {
27
+ :headers => {'Some-Key' => 'Some Value'},
28
+ :times => 1
29
+ }
30
+ end
31
+
32
+ it "uses method injector to modify request" do
33
+ WebMock.enable!
34
+
35
+ http_client = HttpClient.new(@environment)
36
+
37
+ def _inj(req)
38
+ req.headers["Some-Key"] = "Some Value"
39
+ end
40
+
41
+ http_client.add_injector(&method(:_inj))
42
+
43
+ req = OpenStruct.new({:verb => "GET", :path => "/"})
44
+
45
+ stub_request(:any, @environment.base_url)
46
+
47
+ http_client.execute(req)
48
+
49
+ assert_requested :get, "#{@environment.base_url}/", {
50
+ :headers => {'Some-Key' => 'Some Value'},
51
+ :times => 1
52
+ }
53
+ end
54
+
55
+ it "sets User-Agent header in request if not set" do
56
+ WebMock.enable!
57
+
58
+ http_client = HttpClient.new(@environment)
59
+ req = OpenStruct.new({:verb => "GET", :path => "/"})
60
+
61
+ stub_request(:any, @environment.base_url)
62
+
63
+ http_client.execute(req)
64
+
65
+ assert_requested :get, "#{@environment.base_url}/", {
66
+ :headers => {"User-Agent" => "PayPalHttp-Ruby HTTP/1.1"},
67
+ :times => 1
68
+ }
69
+ end
70
+
71
+ it "does not overwrite User-Agent header if set" do
72
+ WebMock.enable!
73
+
74
+ http_client = HttpClient.new(@environment)
75
+
76
+ req = OpenStruct.new({:verb => "GET", :path => "/", :headers => {"User-Agent" => "Not Ruby Http/1.1"}})
77
+
78
+ stub_request(:any, @environment.base_url)
79
+
80
+ http_client.execute(req)
81
+
82
+ assert_requested :get, "#{@environment.base_url}/", {
83
+ :headers => {"User-Agent" => "Not Ruby Http/1.1"},
84
+ :times => 1
85
+ }
86
+ end
87
+
88
+ it "does not modify the original request" do
89
+ WebMock.enable!
90
+
91
+ http_client = HttpClient.new(@environment)
92
+
93
+ req = OpenStruct.new({:verb => "GET", :path => "/"})
94
+
95
+ stub_request(:any, @environment.base_url)
96
+
97
+ http_client.execute(req)
98
+
99
+ expect(req.headers).to be_nil
100
+ end
101
+
102
+ it "uses body in request" do
103
+ WebMock.enable!
104
+
105
+ stub_request(:delete, @environment.base_url + "/path")
106
+
107
+ req = OpenStruct.new({
108
+ :verb => "DELETE",
109
+ :path => "/path",
110
+ :headers => {
111
+ "Content-Type" => "text/plain"
112
+ }
113
+ })
114
+
115
+ req.body = "I want to delete the thing"
116
+
117
+ http_client = HttpClient.new(@environment)
118
+
119
+ resp = http_client.execute(req)
120
+ expect(resp.status_code).to eq(200)
121
+
122
+ assert_requested(:delete, @environment.base_url + "/path") { |requested| requested.body == "I want to delete the thing" }
123
+ end
124
+
125
+ it "parses 200 level response" do
126
+ WebMock.enable!
127
+
128
+ return_data = JSON.generate({
129
+ :some_key => "value"
130
+ })
131
+
132
+ stub_request(:any, @environment.base_url).
133
+ to_return(body: return_data, status: 204,
134
+ headers: {
135
+ 'Some-Weird-Header' => 'Some weird value',
136
+ 'Content-Type' => 'application/json'
137
+ })
138
+
139
+ http_client = HttpClient.new(@environment)
140
+ req = OpenStruct.new({:verb => "GET", :path => "/"})
141
+
142
+ resp = http_client.execute(req)
143
+
144
+ expect(resp.status_code).to eq(204)
145
+ expect(resp.result.some_key).to eq('value')
146
+ expect(resp.headers["Some-Weird-Header".downcase]).to eq(["Some weird value"])
147
+ end
148
+
149
+ it "throws for non-200 level response" do
150
+ WebMock.enable!
151
+
152
+ return_data = {
153
+ :error => "error message",
154
+ :another_key => 1013
155
+ }
156
+
157
+ json = JSON.generate(return_data)
158
+
159
+ stub_request(:any, @environment.base_url).
160
+ to_return(body: json, status: 400,
161
+ headers: {
162
+ 'Some-Weird-Header' => 'Some weird value',
163
+ 'Content-Type' => 'application/json'
164
+ })
165
+
166
+ http_client = HttpClient.new(@environment)
167
+ req = OpenStruct.new({:verb => "GET", :path => URI(@environment.base_url)})
168
+
169
+ begin
170
+ resp = http_client.execute(req)
171
+ fail
172
+ rescue => e
173
+ resp = e
174
+ expect(resp.status_code).to eq(400)
175
+ expect(resp.result.error).to eq('error message')
176
+ expect(resp.result.another_key).to eq(1013)
177
+ expect(resp.headers["Some-Weird-Header".downcase]).to eq(["Some weird value"])
178
+ end
179
+ end
180
+
181
+ it "makes request when only a path is specified" do
182
+ WebMock.enable!
183
+
184
+ stub_request(:any, @environment.base_url + "/v1/api")
185
+ .to_return(status: 200)
186
+
187
+ http_client = HttpClient.new(@environment)
188
+ req = OpenStruct.new({:verb => "GET", :path => "/v1/api"})
189
+
190
+ resp = http_client.execute(req)
191
+ expect(resp.status_code).to eq(200)
192
+ end
193
+
194
+ it 'uses encoder to serialize requests by default' do
195
+ WebMock.enable!
196
+
197
+ return_data = {
198
+ :key => "value"
199
+ }
200
+
201
+ http_client = HttpClient.new(@environment)
202
+ stub_request(:get, @environment.base_url + "/v1/api")
203
+ .to_return(body: JSON.generate(return_data), status: 200, headers: {"Content-Type" => "application/json"})
204
+
205
+ req = OpenStruct.new({:verb => "GET", :path => "/v1/api"})
206
+ resp = http_client.execute(req)
207
+
208
+ expect(resp.status_code).to eq(200)
209
+ expect(resp.result.key).to eq('value')
210
+ end
211
+
212
+ it 'handles json array result' do
213
+ WebMock.enable!
214
+
215
+ return_data = ["one", "two"]
216
+
217
+ http_client = HttpClient.new(@environment)
218
+
219
+ stub_request(:get, @environment.base_url + "/v1/api")
220
+ .to_return(body: JSON.generate(return_data), status: 200, headers: {"Content-Type" => "application/json"})
221
+
222
+ req = OpenStruct.new({:verb => "GET", :path => "/v1/api"})
223
+
224
+ resp = http_client.execute(req)
225
+
226
+ expect(resp.result).to eq(return_data)
227
+ end
228
+
229
+ it 'deserializes nested response object into nested openstruct response' do
230
+ WebMock.enable!
231
+
232
+ return_data = {
233
+ :key => 'value',
234
+ :nested_key => {
235
+ :string => 'stringvalue',
236
+ :some_hash => {
237
+ :some_int => 1
238
+ },
239
+ :some_array => [[{
240
+ :array_key => 'array-value'
241
+ }]]
242
+ }
243
+ }
244
+
245
+ http_client = HttpClient.new(@environment)
246
+
247
+ stub_request(:get, @environment.base_url + "/v1/api")
248
+ .to_return(body: JSON.generate(return_data), status: 200, headers: {"Content-Type" => "application/json"})
249
+
250
+ req = OpenStruct.new({:verb => "GET", :path => "/v1/api"})
251
+
252
+ resp = http_client.execute(req)
253
+
254
+ expect(resp.result.key).to eq('value')
255
+ expect(resp.result.nested_key.string).to eq('stringvalue')
256
+ expect(resp.result.nested_key.some_hash.some_int).to eq(1)
257
+ expect(resp.result.nested_key.some_array[0][0].array_key).to eq('array-value')
258
+ end
259
+
260
+ it "does not error if no file or body present on a request class" do
261
+ class Request
262
+
263
+ attr_accessor :path, :body, :headers, :verb, :file
264
+
265
+ def initialize()
266
+ @headers = {}
267
+ @verb = "POST"
268
+ @path = "/v1/api"
269
+ end
270
+ end
271
+
272
+ WebMock.enable!
273
+
274
+ stub_request(:any, @environment.base_url + "/v1/api")
275
+ http_client = HttpClient.new(@environment)
276
+
277
+ begin
278
+ http_client.execute(Request.new)
279
+ rescue Exception => e
280
+ fail e.message
281
+ end
282
+ end
283
+ end
284
+
@@ -0,0 +1,33 @@
1
+ describe FormPart do
2
+ describe 'initialize' do
3
+ it 'Header-Cases lower-case headers' do
4
+ lowerCaseFormPart = FormPart.new({:key => 'val'}, {'content-type': 'application/json'});
5
+
6
+ expect(lowerCaseFormPart.headers.keys).to include('Content-Type')
7
+ expect(lowerCaseFormPart.headers.keys.length).to eq(1)
8
+ end
9
+
10
+ it 'Header-Cases single char headers' do
11
+ singleCharFormPart = FormPart.new({:key => 'val'}, {'x': 'application/json'});
12
+
13
+ expect(singleCharFormPart.headers.keys).to include('X')
14
+ expect(singleCharFormPart.headers.keys.length).to eq(1)
15
+ end
16
+
17
+ it 'Header-Cases keeps single header if collision' do
18
+ multiHeaderFormPart = FormPart.new({:key => 'val'}, {'content-type': 'application/json', 'CONTENT-TYPE': 'application/pdf'});
19
+
20
+ expect(multiHeaderFormPart.headers.keys).to include('Content-Type')
21
+ expect(multiHeaderFormPart.headers.keys.length).to eq(1)
22
+ end
23
+
24
+ it 'Header-Cases multiple headers when supplied' do
25
+ multiHeaderFormPart = FormPart.new({:key => 'val'}, {'header-one': 'application/json', 'header-Two': 'application/pdf', 'HEADER-THREE': 'img/jpg'});
26
+
27
+ expect(multiHeaderFormPart.headers.keys).to include('Header-One')
28
+ expect(multiHeaderFormPart.headers.keys).to include('Header-Two')
29
+ expect(multiHeaderFormPart.headers.keys).to include('Header-Three')
30
+ expect(multiHeaderFormPart.headers.keys.length).to eq(3)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ describe Multipart do
2
+ describe 'mime_type_for_filename' do
3
+ it 'supports gif' do
4
+ expect(Multipart.new()._mime_type_for_file_name("test.gif")).to eq('image/gif')
5
+ end
6
+
7
+ it 'supports jpeg' do
8
+ expect(Multipart.new()._mime_type_for_file_name("test.jpeg")).to eq('image/jpeg')
9
+ end
10
+
11
+ it 'supports jpg' do
12
+ expect(Multipart.new()._mime_type_for_file_name("test.jpg")).to eq('image/jpeg')
13
+ end
14
+
15
+ it 'supports pdf' do
16
+ expect(Multipart.new()._mime_type_for_file_name("test.pdf")).to eq('application/pdf')
17
+ end
18
+
19
+ it 'supports appication/octet-stream' do
20
+ expect(Multipart.new()._mime_type_for_file_name("test.random")).to eq('application/octet-stream')
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,109 @@
1
+ require 'webmock/rspec'
2
+ require_relative "../lib/paypalhttp.rb"
3
+ include WebMock::API
4
+ include PayPalHttp
5
+
6
+
7
+ # This file was generated by the `rspec --init` command. Conventionally, all
8
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
9
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
10
+ # this file to always be loaded, without a need to explicitly require it in any
11
+ # files.
12
+ #
13
+ # Given that it is always loaded, you are encouraged to keep this file as
14
+ # light-weight as possible. Requiring heavyweight dependencies from this file
15
+ # will add to the boot time of your test suite on EVERY test run, even for an
16
+ # individual file that may not need all of that loaded. Instead, consider making
17
+ # a separate helper file that requires the additional dependencies and performs
18
+ # the additional setup, and require it from the spec files that actually need
19
+ # it.
20
+ #
21
+ # The `.rspec` file also contains a few flags that are not defaults but that
22
+ # users commonly want.
23
+ #
24
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
25
+ RSpec.configure do |config|
26
+ # rspec-expectations config goes here. You can use an alternate
27
+ # assertion/expectation library such as wrong or the stdlib/minitest
28
+ # assertions if you prefer.
29
+ config.expect_with :rspec do |expectations|
30
+ # This option will default to `true` in RSpec 4. It makes the `description`
31
+ # and `failure_message` of custom matchers include text for helper methods
32
+ # defined using `chain`, e.g.:
33
+ # be_bigger_than(2).and_smaller_than(4).description
34
+ # # => "be bigger than 2 and smaller than 4"
35
+ # ...rather than:
36
+ # # => "be bigger than 2"
37
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
38
+ end
39
+
40
+ # rspec-mocks config goes here. You can use an alternate test double
41
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
42
+ config.mock_with :rspec do |mocks|
43
+ # Prevents you from mocking or stubbing a method that does not exist on
44
+ # a real object. This is generally recommended, and will default to
45
+ # `true` in RSpec 4.
46
+ mocks.verify_partial_doubles = true
47
+ end
48
+
49
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
50
+ # have no way to turn it off -- the option exists only for backwards
51
+ # compatibility in RSpec 3). It causes shared context metadata to be
52
+ # inherited by the metadata hash of host groups and examples, rather than
53
+ # triggering implicit auto-inclusion in groups with matching metadata.
54
+ config.shared_context_metadata_behavior = :apply_to_host_groups
55
+
56
+ # The settings below are suggested to provide a good initial experience
57
+ # with RSpec, but feel free to customize to your heart's content.
58
+ =begin
59
+ # This allows you to limit a spec run to individual examples or groups
60
+ # you care about by tagging them with `:focus` metadata. When nothing
61
+ # is tagged with `:focus`, all examples get run. RSpec also provides
62
+ # aliases for `it`, `describe`, and `context` that include `:focus`
63
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
64
+ config.filter_run_when_matching :focus
65
+
66
+ # Allows RSpec to persist some state between runs in order to support
67
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
68
+ # you configure your source control system to ignore this file.
69
+ config.example_status_persistence_file_path = "spec/examples.txt"
70
+
71
+ # Limits the available syntax to the non-monkey patched syntax that is
72
+ # recommended. For more details, see:
73
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
74
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
75
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
76
+ config.disable_monkey_patching!
77
+
78
+ # This setting enables warnings. It's recommended, but in some cases may
79
+ # be too noisy due to issues in dependencies.
80
+ config.warnings = true
81
+
82
+ # Many RSpec users commonly either run the entire suite or an individual
83
+ # file, and it's useful to allow more verbose output when running an
84
+ # individual spec file.
85
+ if config.files_to_run.one?
86
+ # Use the documentation formatter for detailed output,
87
+ # unless a formatter has already been configured
88
+ # (e.g. via a command-line flag).
89
+ config.default_formatter = 'doc'
90
+ end
91
+
92
+ # Print the 10 slowest examples and example groups at the
93
+ # end of the spec run, to help surface which specs are running
94
+ # particularly slow.
95
+ config.profile_examples = 10
96
+
97
+ # Run specs in random order to surface order dependencies. If you find an
98
+ # order dependency and want to debug it, you can fix the order by providing
99
+ # the seed, which is printed after each run.
100
+ # --seed 1234
101
+ config.order = :random
102
+
103
+ # Seed global randomization in this process using the `--seed` CLI option.
104
+ # Setting this allows you to use `--seed` to deterministically reproduce
105
+ # test failures related to randomization by passing the same `--seed` value
106
+ # as the one that triggered the failure.
107
+ Kernel.srand config.seed
108
+ =end
109
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: paypalhttp
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - PayPal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-11-11 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Used for generated API clients
14
+ email:
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/paypalhttp.rb
20
+ - lib/paypalhttp/encoder.rb
21
+ - lib/paypalhttp/environment.rb
22
+ - lib/paypalhttp/errors.rb
23
+ - lib/paypalhttp/http_client.rb
24
+ - lib/paypalhttp/serializers/form_encoded.rb
25
+ - lib/paypalhttp/serializers/form_part.rb
26
+ - lib/paypalhttp/serializers/json.rb
27
+ - lib/paypalhttp/serializers/multipart.rb
28
+ - lib/paypalhttp/serializers/text.rb
29
+ - lib/paypalhttp/version.rb
30
+ - paypalhttp.gemspec
31
+ - spec/paypalhttp/encoder_spec.rb
32
+ - spec/paypalhttp/http_client_spec.rb
33
+ - spec/paypalhttp/serializers/form_part_spec.rb
34
+ - spec/paypalhttp/serializers/multipart_spec.rb
35
+ - spec/spec_helper.rb
36
+ homepage:
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 3.0.3
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: PayPalHttp Client Library
59
+ test_files: []