paypalhttp 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []