rest_api_builder 0.0.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e04eb4a2a88c29a02d4fc831018d118ce8cd570f7523625cc83bd155041734a
4
- data.tar.gz: 1092c81256426cb4d0e590452c0831291b20697108a07358fe5efdc5761d74b0
3
+ metadata.gz: d192a166c3b73c990be9893ac5f08bfd601aed7c4cc9c8bd8419ce2fe20c32be
4
+ data.tar.gz: 9d9974f0d2451412333dec8979a8bcdd3268b7bba8d15f58d7ef9ce4ccb63f31
5
5
  SHA512:
6
- metadata.gz: b494a395a84ce64096cbe07231457e742de4ddd812bdf092e00e717db0c078496c907d81d19e671f32ce484f8b6464e310aba16184c6ed4724d0c0c19f7a5749
7
- data.tar.gz: 2626417df9c9241d33ac3e76b2a9a5f64638b6a0397785c470f921303fb47188ab606914fc4890b47aaf4fd4fdc6f96fe8c6569ac9a29f114b9854ef66d48c29
6
+ metadata.gz: 23676f348f1feb5cb3b6d66b452af4ced13358ceec0f91591279d0db73e48b02bb06e415252c2a08708afc12fc6916c2d679dcffef3b6f625b58e2194ef36077
7
+ data.tar.gz: ce0e28889a73b8cc2924cc855e3feef7da8dbbca0d78551ad5cfd5c23ade751ffd9e11b42af711d19826614993fa94fa31544c371221e617f94ad06ef3fe57d5
data/LICENSE CHANGED
File without changes
data/README.md CHANGED
@@ -8,154 +8,339 @@ RestClient is great, but after building a few API clients with it you will almos
8
8
  - Handling and extracting details from non-200 responses
9
9
  - Creating testing interfaces for your API clients
10
10
 
11
- This library's tries to solve these and similar issues by providing a thin wrapper around [rest-client](https://github.com/rest-client/rest-client) and an optional [webmock](https://github.com/bblimke/webmock) testing interface for it.
11
+ This library tries to solve these and similar issues by providing a set of self-contained helper methods to improve on [rest-client](https://github.com/rest-client/rest-client) features with an optional [WebMock](https://github.com/bblimke/webmock) testing interface.
12
12
 
13
13
  ## Installation
14
14
  ```
15
15
  gem install rest_api_builder
16
16
  ```
17
17
 
18
- ## WebMock interface installation
19
- Simply require webmock interface before your test, for example in your `test_helper.rb`:
18
+ ## RestAPIBuilder::Request
19
+ Main RestAPIBuilder module which includes various helper methods for parsing RestClient responses, catching errors and composing request details. `handle_*` and `compose_*` methods are intended to be used in conjunction, but you can use any of them in any combination without relying on the rest.
20
+
21
+ ```rb
22
+ # Basic usage
23
+ require 'rest_api_builder'
24
+ include RestAPIBuilder::Request
25
+
26
+ logger = Logger.new(STDOUT)
27
+ response = handle_json_response(logger: logger) do
28
+ RestClient::Request.execute(
29
+ {
30
+ **compose_json_request_options(
31
+ base_url: 'https://api.github.com',
32
+ path: '/users/octocat/orgs',
33
+ method: :get
34
+ ),
35
+ log: logger
36
+ }
37
+ )
38
+ end
39
+
40
+ response[:success] # => true
41
+ response[:status] # => 200
42
+ response[:body] # => []
43
+ ```
44
+
45
+ Included methods:
46
+
47
+ ### `#handle_response(options, &block)`
48
+ Executes given block, expecting to receive RestClient::Response as a result.\
49
+ Returns plain ruby hash with following keys: `:success, :status, :body, :headers`\
50
+ This will gracefully handle non-200 responses, but will throw on any error without defined response(e.g server timeout)
51
+
52
+ ```rb
53
+ require 'rest_api_builder'
54
+ include RestAPIBuilder::Request
55
+
56
+ # normal response
57
+ response = handle_response do
58
+ RestClient::Request.execute(method: :get, url: 'https://api.github.com/users/octocat/orgs')
59
+ end
60
+
61
+ response[:success] # => true
62
+ response[:status] # => 200
63
+ response[:body] # => '[]'
64
+ response[:headers] # => {:accept_ranges=>"bytes", :access_control_allow_origin=>"*", ...}
65
+
66
+ # non-200 response that would result in RestClient::RequestFailed exception otherwise
67
+ response = handle_response do
68
+ RestClient::Request.execute(method: :get, url: 'https://api.github.com/users/octocat/foobar')
69
+ end
70
+
71
+ response[:success] # => false
72
+ response[:status] # => 404
73
+ response[:body] # => "{\"message\":\"Not Found\",..."}"
74
+ ```
75
+
76
+ #### Accepted Options:
77
+ | Name | Description |
78
+ |--------|-------------|
79
+ | logger | Any object with `<<` method, e.g `Logger` instance. Will be used to log *response* details in the same way that [RestClient's `log` option](https://github.com/rest-client/rest-client#logging) logs the request details. Optional |
80
+
81
+ ### `#handle_json_response(options, &block)`
82
+ Behaves just like `#handle_response`, but will also attempt to decode response `:body`, returning it as is if a parsing error occurs.
83
+
84
+ ```rb
85
+ require 'rest_api_builder'
86
+ include RestAPIBuilder::Request
87
+
88
+ # decodes JSON response body
89
+ response = handle_json_response do
90
+ RestClient::Request.execute(method: :get, url: 'https://api.github.com/users/octocat/orgs')
91
+ end
92
+
93
+ response[:success] # => true
94
+ response[:status] # => 200
95
+ response[:body] # => []
96
+
97
+ # returns body as is if it cannot be decoded
98
+ response = handle_json_response do
99
+ RestClient::Request.execute(method: :get, url: 'https://github.com/foo/bar/test')
100
+ end
101
+
102
+ response[:success] # => false
103
+ response[:status] # => 404
104
+ response[:body] # => "Not Found"
105
+ ```
106
+
107
+ ### `handle_response_error(&block)`
108
+ Low-level API.\
109
+ You can use this method if you want to work with regular `RestClient::Response` objects directly(e.g when using `block_response` or `raw_response` options). This will handle non-200 exceptions but will not do anything else.\
110
+ Returns plain ruby hash with `:success` and `:raw_response` keys.
111
+
112
+ ```rb
113
+ require 'rest_api_builder'
114
+ include RestAPIBuilder::Request
115
+
116
+ # returns RestClient::Response as :raw_response
117
+ response = handle_response_error do
118
+ RestClient::Request.execute(method: :get, url: 'https://api.github.com/users/octocat/orgs')
119
+ end
120
+
121
+ response[:success] # => true
122
+ response[:raw_response] # => <RestClient::Response 200 "[]">
123
+
124
+ # handles non-200 responses
125
+ response = handle_response_error do
126
+ RestClient::Request.execute(
127
+ method: :get,
128
+ url: 'https://api.github.com/users/octocat/foobar',
129
+ raw_response: true
130
+ )
131
+ end
132
+
133
+ response[:success] # => false
134
+ response[:raw_response] # => <RestClient::RawResponse @code=404, @file=#<Tempfile...>>
135
+ ```
136
+
137
+ ### `#compose_request_options(options)`
138
+ Provides a more consistent interface for `RestClient::Request#execute`.\
139
+ This method returns a hash of options which you can then pass to `RestClient::Request#execute`.
140
+
141
+ ```rb
142
+ require 'rest_api_builder'
143
+ include RestAPIBuilder::Request
144
+
145
+ # basic usage
146
+ response = RestClient::Request.execute(
147
+ compose_request_options(
148
+ base_url: 'https://api.github.com',
149
+ path: '/users/octocat/orgs',
150
+ method: :get
151
+ )
152
+ )
153
+
154
+ response.request.url # => "https://api.github.com/users/octocat/orgs"
155
+ response.body # => '[]'
156
+
157
+ # advanced options
158
+ result = handle_response_error do
159
+ RestClient::Request.execute(
160
+ compose_request_options(
161
+ base_url: 'https://api.github.com',
162
+ path: '/users/octocat/orgs',
163
+ method: :post,
164
+ body: 'Hello',
165
+ headers: { content_type: 'foobar' },
166
+ query: { foo: 'bar' }
167
+ )
168
+ )
169
+ end
170
+ request = result[:raw_response].request
171
+
172
+ request.url # => "https://api.github.com/users/octocat/orgs?foo=bar"
173
+ request.headers # => {:content_type=>"foobar"}
174
+ request.payload # => <RestClient::Payload 'Hello'>
175
+ ```
176
+
177
+ #### Accepted Options:
178
+ | Name | Description |
179
+ |----------|-------------|
180
+ | base_url | Base URL of the request. Required. |
181
+ | method | HTTP method of the request(e.g :get, :post, :patch). Required. |
182
+ | path | Path to be appended to `base_url`. Optional. |
183
+ | body | Request Body. Optional. |
184
+ | headers | Request Headers. Optional. |
185
+ | query | Query hash to be appended to the resulting url. Optional. |
186
+
187
+ ### `#compose_json_request_options(options)`
188
+ Same as `compose_request_options` but will also convert provided `body`(if any) to JSON and append `Content-Type: 'application/json'` to `headers`
189
+
20
190
  ```rb
21
- # test_helper.rb
22
- require "webmock"
23
- require "rest_api_builder/webmock_request_expectations"
191
+ require 'rest_api_builder'
192
+ include RestAPIBuilder::Request
193
+
194
+ # basic usage
195
+ result = handle_response_error do
196
+ RestClient::Request.execute(
197
+ compose_json_request_options(
198
+ base_url: 'https://api.github.com',
199
+ path: '/users/octocat/orgs',
200
+ method: :post,
201
+ body: {a: 1}
202
+ )
203
+ )
204
+ end
205
+ request = result[:raw_response].request
206
+
207
+ request.headers # => {:content_type=>:json}
208
+ request.payload # => <RestClient::Payload "{\"a\":1}">
209
+ ```
210
+
211
+ ## RestAPIBuilder::APIClient
24
212
 
25
- WebMock.enable!
213
+ ### `#define_resource_shortcuts(resources, resources_scope:, init_with:)`
214
+ Dynamically defines attribute readers for given resources
26
215
 
27
- # my_spec.rb
28
- require 'test_helper'
216
+ ```rb
217
+ require 'rest_api_builder'
218
+
219
+ module ReadmeExamples
220
+ module Resources
221
+ class Octocat
222
+ def orgs
223
+ RestClient::Request.execute(method: :get, url: 'https://api.github.com/users/octocat/orgs')
224
+ end
225
+ end
226
+ end
29
227
 
30
- describe 'my test' do
31
- it 'performs a request' do
32
- RestAPIBuilder::WebMockRequestExpectations.expect_execute(...).to_return(body: "hi!")
33
- result = RestAPIBuilder::Request.execute(...)
228
+ class APIClient
229
+ include RestAPIBuilder::APIClient
34
230
 
35
- # some assertions
231
+ def initialize
232
+ define_resource_shortcuts(
233
+ [:octocat],
234
+ resources_scope: ReadmeExamples::Resources,
235
+ init_with: ->(resource_class) { resource_class.new }
236
+ )
36
237
  end
37
238
  end
239
+ end
240
+
241
+
242
+ GITHUB_API = ReadmeExamples::APIClient.new
243
+
244
+ response = GITHUB_API.octocat.orgs
245
+ response.body # => '[]'
246
+ response.code # => 200
38
247
  ```
39
248
 
40
- `RestAPIBuilder::WebMockRequestExpectations` expects that you have WebMock installed as a dependency.
249
+ #### Accepted Arguments:
250
+ | Name | Description |
251
+ |-----------------|-------------|
252
+ | resources | Array of resources to define shortcuts for |
253
+ | resources_scope | Module or String(path to Module) within which resource classes are contained |
254
+ | init_with | Lambda which will be called for each resource class. The result will be returned from the defined shortcut. **Note:** `init_with` lambda is only called once so resource class must be able to function as a singleton. |
255
+
256
+ ## RestAPIBuilder::WebMockRequestExpectations
257
+ Optional wrapper around WebMock mocking interface with various improvements. This module must be required explicitly and expects [WebMock](https://github.com/bblimke/webmock) to be installed as a dependency in your project.
41
258
 
42
- ## Usage
259
+ ### `#expect_execute(options)`
260
+ Defines a request expectation using WebMock's `stub_request`.
43
261
  ```rb
44
- require "rest_api_builder"
45
-
46
- Request = RestAPIBuilder::Request
47
-
48
- # Simple request:
49
- response = Request.execute(base_url: "example.com", method: :get)
50
- response[:success] #=> true
51
- response[:status] #=> 200
52
- response[:body] #=> "<!doctype html>\n<html>..."
53
- response[:headers] #=> {:accept_ranges=>"bytes", ...}
54
-
55
- # Non-200 responses:
56
- response = Request.execute(base_url: "example.com", path: "/foo", method: :get)
57
- response[:success] #=> false
58
- response[:status] #=> 404
59
- response[:body] #=> "<!doctype html>\n<html>..."
60
-
61
- # JSON requests:
62
- response = Request.json_execute(base_url: "api.github.com", path: "/users/octocat/orgs", method: :get)
63
- response[:success] #=> true
64
- response[:body] #=> []
262
+ require 'rest_api_builder'
263
+ require 'rest_api_builder/webmock_request_expectations'
264
+ include RestAPIBuilder::Request
265
+ include RestAPIBuilder::WebMockRequestExpectations
266
+
267
+ # basic usage with regular webmock interface
268
+ expect_execute(
269
+ base_url: 'https://api.github.com',
270
+ path: '/users/octocat/orgs',
271
+ method: :post
272
+ ).with(body: {foo: 'bar'}).to_return(body: '[hello]')
273
+
274
+ response = RestClient::Request.execute(
275
+ compose_request_options(
276
+ base_url: 'https://api.github.com',
277
+ path: '/users/octocat/orgs',
278
+ method: :post,
279
+ body: {foo: 'bar'}
280
+ )
281
+ )
282
+
283
+ response.body # => '[hello]'
284
+
285
+ # using expect_execute's request and response options
286
+ expect_execute(
287
+ base_url: 'https://api.github.com',
288
+ path: '/users/octocat',
289
+ method: :post,
290
+ # matches request body and query hashes partially by default
291
+ request: { body: {foo: 'bar'}, query: {a: 1, b: 2} },
292
+ response: { body: 'hello' }
293
+ )
294
+
295
+ response = RestClient::Request.execute(
296
+ compose_request_options(
297
+ base_url: 'https://api.github.com',
298
+ path: '/users/octocat',
299
+ method: :post,
300
+ body: { foo: 'bar', bar: 'baz' },
301
+ query: { a: 1, b: 2 }
302
+ )
303
+ )
304
+
305
+ response.body # => 'hello'
65
306
  ```
66
307
 
67
- ## WebMock Expectations
308
+ #### Accepted Options:
309
+ | Name | Description |
310
+ |----------|-------------|
311
+ | base_url | Base URL of the request expectation. Required. |
312
+ | path | HTTP method of the request. Required. |
313
+ | method | Path to be appended to `base_url`. Regular expressions are also supported. Optional. |
314
+ | request | Hash of options which will be passed to WebMock's `with` method with following changes: `body` hash is converted to `hash_including` expectation and `query` hash values are transformed to strings and then it's converted into `hash_including` expectation. Optional |
315
+ | response | Hash of options which will be passed to WebMock's `to_return` method unchanged. Optional |
316
+
317
+ ### `#expect_json_execute(options)`
318
+ Same as `expect_execute` but will also call JSON encode on `response.body`(if one is provided).
319
+
68
320
  ```rb
69
- require "rest_api_builder"
70
- require "webmock"
71
- require "rest_api_builder/webmock_request_expectations"
72
-
73
- WebMock.disable_net_connect!
74
-
75
- Request = RestAPIBuilder::Request
76
- Expectations = RestAPIBuilder::WebMockRequestExpectations
77
-
78
- # Simple expectation
79
- Expectations.expect_execute(base_url: "test.com", method: :get)
80
- response = Request.execute(base_url: "test.com", method: :get)
81
-
82
- response[:success] #=> true
83
- response[:status] #=> 200
84
- response[:body] #=> ''
85
- response[:headers] #=> {}
86
-
87
- # Specifying expectation details with WebMock::Request methods
88
- Expectations
89
- .expect_execute(base_url: "test.com", method: :get)
90
- .to_return(status: 404, body: "not found")
91
- response = Request.execute(base_url: "test.com", method: :get)
92
-
93
- response[:success] #=> false
94
- response[:status] #=> 404
95
- response[:body] #=> "not found"
96
-
97
- # Specifying expectation details with :request and :response options
98
- Expectations.expect_execute(
99
- base_url: "test.com",
100
- method: :post,
101
- response: { body: 'hello' },
102
- request: { body: WebMock::API.hash_including({foo: "bar"}) }
321
+ require 'rest_api_builder'
322
+ require 'rest_api_builder/webmock_request_expectations'
323
+ include RestAPIBuilder::Request
324
+ include RestAPIBuilder::WebMockRequestExpectations
325
+
326
+ expect_json_execute(
327
+ base_url: 'https://api.github.com',
328
+ path: '/users/octocat/orgs',
329
+ method: :get,
330
+ response: { body: { foo: 'bar' } }
103
331
  )
104
- response = Request.json_execute(base_url: "test.com", method: :post, body: {foo: "bar"})
105
- response[:success] #=> true
106
- response[:body] #=> 'hello'
107
332
 
108
- Request.json_execute(base_url: "test.com", method: :post, body: {bar: "baz"}) # => Raises WebMock::NetConnectNotAllowedError
109
-
110
- # Using #expect_json_execute
111
- Expectations.expect_json_execute(
112
- base_url: "test.com",
113
- method: :get,
114
- response: { body: {hi: 'hello'} }
333
+ response = RestClient::Request.execute(
334
+ compose_request_options(
335
+ base_url: 'https://api.github.com',
336
+ path: '/users/octocat/orgs',
337
+ method: :get
338
+ )
115
339
  )
116
- response = Request.execute(base_url: "test.com", method: :get)
117
- response[:success] #=> true
118
- response[:body] #=> "{\"hi\":\"hello\"}"
340
+
341
+ response.body # => "{\"foo\":\"bar\"}"
119
342
  ```
120
343
 
121
- ## Request API
122
- ### RestAPIBuilder::Request.execute(options)
123
- Performs a HTTP request via `RestClient::Request.execute`.\
124
- Returns ruby hash with following keys: `:success`, `:status`, `:body`, `:headers`\
125
- Does not throw on non-200 responses like RestClient does, but will throw on any error without defined response(e.g server timeout)
126
-
127
- #### Options:
128
- * **base_url**: Base URL of the request. Required.
129
- * **method**: HTTP method of the request(e.g :get, :post, :patch). Required.
130
- * **path**: Path to be appended to the :base_url. Optional.
131
- * **body**: Request Body. Optional.
132
- * **headers**: Request Headers. Optional.
133
- * **query**: Query hash to be appended to the resulting url. Optional.
134
- * **logger**: A `Logger` instance to be passed to RestClient in `log` option. Will also log response details as RestClient does not do this by default. Optional
135
- * **parse_json**: Boolean. If `true`, will attempt to parse the response body as JSON. Will return the response body unchanged if it does not contain valid JSON. `false` by default.
136
- * **raw_response**: Boolean. If `true`, the response returned by RestClient will not be parsed into {:status, :body, :headers}, but instead returned as {:raw_response}. `false` by default.
137
- * **rest_client_options**: Any additional options to be passed to `RestClient::Request.execute` unchanged. **Any option set here will completely overwrite all custom options**. For example, if you call `RestAPIBuilder::Request.execute(method: :post, rest_client_options: {method: :get})`, the resulting request will be sent as GET. Optional.
138
-
139
- ### RestAPIBuilder::Request.json_execute(options)
140
- A convenience shortcut for `#execute` which will also:
141
- - Add `Content-Type: 'application/json'` to request `headers`
142
- - Convert request `body` to JSON
143
- - Set `parse_json` option to `true`
144
-
145
-
146
- ## WebMockRequestExpectations API
147
- ### RestAPIBuilder::WebMockRequestExpectations.expect_execute(options)
148
- Defines a request expectation using WebMock's `stub_request`. Returns an instance of `WebMock::RequestStub` on which methods such as `with`, `to_return`, `to_timeout` can be called
149
-
150
- #### Options:
151
- * **base_url**: Base URL of the request. Required.
152
- * **method**: HTTP method of the request(e.g :get, :post, :patch). Required.
153
- * **path**: Path to be appended to the :base_url. Optional.
154
- * **request**: request details which will be passed to `WebMock::RequestStub#with` if provided. Optional
155
- * **response**: response details which will be passed to `WebMock::RequestStub#to_return` if provided. Optional
156
-
157
- ### RestAPIBuilder::WebMockRequestExpectations.expect_json_execute(options)
158
- A convenience shortcut for `#json_execute` which will convert `request[:body]` to JSON if it's provided
159
344
 
160
345
  ## License
161
- MIT
346
+ MIT
@@ -1,21 +1,3 @@
1
1
  require 'forwardable'
2
2
  require 'rest_api_builder/request'
3
- require 'rest_api_builder/request_options'
4
- require 'rest_api_builder/response_handler'
5
-
6
- module RestAPIBuilder
7
- extend Forwardable
8
-
9
- def_delegator :request_options, :compose, :compose_request_options
10
- def_delegator :request_options, :compose_json, :compose_json_request_options
11
-
12
- def_delegators :response_handler, :handle_response, :handle_json_response, :handle_response_error
13
-
14
- def request_options
15
- RequestOptions.new
16
- end
17
-
18
- def response_handler
19
- ResponseHandler.new
20
- end
21
- end
3
+ require 'rest_api_builder/api_client'
@@ -0,0 +1,16 @@
1
+ require 'rest_api_builder/helpers/string_helper'
2
+
3
+ module RestAPIBuilder
4
+ module APIClient
5
+ def define_resource_shortcuts(resources, resources_scope:, init_with:)
6
+ resources.each do |name|
7
+ class_name = RestAPIBuilder::Helpers::StringHelper.camelize(name.to_s)
8
+ resource_class = Object.const_get("#{resources_scope}::#{class_name}")
9
+
10
+ define_singleton_method(name) do
11
+ init_with.call(resource_class)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ module RestAPIBuilder
2
+ module Helpers
3
+ module StringHelper
4
+ module_function
5
+
6
+ # From https://apidock.com/rails/String/camelize
7
+ def camelize(string)
8
+ string
9
+ .sub(/^[a-z\d]*/, &:capitalize)
10
+ .gsub(/(?:_|(\/))([a-z\d]*)/) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }
11
+ .gsub("/", "::")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module RestAPIBuilder
2
+ module Helpers
3
+ module UrlHelper
4
+ module_function
5
+
6
+ def full_url(url, path)
7
+ if path
8
+ File.join(url, path)
9
+ else
10
+ url
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,46 +1,18 @@
1
- require 'rest-client'
1
+ require 'forwardable'
2
+ require 'rest_api_builder/request/request_options'
3
+ require 'rest_api_builder/request/response_handler'
2
4
 
3
5
  module RestAPIBuilder
4
- class RequestSingleton
5
- def json_execute(headers: {}, body: nil, **options)
6
- headers = headers.merge(content_type: :json)
7
- body &&= JSON.generate(body)
8
- execute(**options, parse_json: true, headers: headers, body: body)
9
- end
6
+ module Request
7
+ extend Forwardable
10
8
 
11
- def execute(
12
- base_url:,
13
- method:,
14
- body: nil,
15
- headers: {},
16
- query: nil,
17
- path: nil,
18
- logger: nil,
19
- parse_json: false,
20
- raw_response: false,
21
- rest_client_options: {}
22
- )
23
- options = RequestOptions.new.compose(
24
- base_url: base_url,
25
- method: method,
26
- body: body,
27
- headers: headers,
28
- query: query,
29
- path: path
30
- )
9
+ def_delegators RestAPIBuilder::Request::RequestOptions,
10
+ :compose_request_options,
11
+ :compose_json_request_options
31
12
 
32
- response_handler = ResponseHandler.new
33
- execute_request = proc { RestClient::Request.execute(**options, log: logger, **rest_client_options) }
34
-
35
- if raw_response
36
- response_handler.handle_response_error(&execute_request)
37
- elsif parse_json
38
- response_handler.handle_json_response(logger: logger, &execute_request)
39
- else
40
- response_handler.handle_response(logger: logger, &execute_request)
41
- end
42
- end
13
+ def_delegators RestAPIBuilder::Request::ResponseHandler,
14
+ :handle_response,
15
+ :handle_json_response,
16
+ :handle_response_error
43
17
  end
44
-
45
- Request = RequestSingleton.new
46
18
  end
@@ -0,0 +1,37 @@
1
+ require 'rest_api_builder/helpers/url_helper'
2
+
3
+ module RestAPIBuilder
4
+ module Request
5
+ module RequestOptions
6
+ extend RestAPIBuilder::Helpers::UrlHelper
7
+
8
+ module_function
9
+
10
+ def compose_request_options(base_url:, method:, path: nil, body: nil, headers: {}, query: nil)
11
+ if method == :get && body
12
+ raise ArgumentError, 'GET requests do not support body'
13
+ end
14
+
15
+ headers = headers.merge(params: query) if query
16
+
17
+ {
18
+ method: method,
19
+ url: full_url(base_url, path),
20
+ payload: body,
21
+ headers: headers
22
+ }
23
+ end
24
+
25
+ def compose_json_request_options(**options)
26
+ result = compose_request_options(**options)
27
+ headers = result[:headers]
28
+ payload = result[:payload]
29
+
30
+ result.merge(
31
+ headers: headers.merge(content_type: :json),
32
+ payload: payload && JSON.generate(payload)
33
+ )
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,48 @@
1
+ require 'rest-client'
2
+
3
+ module RestAPIBuilder
4
+ module Request
5
+ module ResponseHandler
6
+ module_function
7
+
8
+ def handle_json_response(**options, &block)
9
+ result = handle_response(**options, &block)
10
+ result.merge(body: parse_json(result[:body]))
11
+ end
12
+
13
+ def handle_response(logger: nil, &block)
14
+ result = parse_response(**handle_response_error(&block))
15
+ maybe_log_result(result, logger: logger)
16
+ result
17
+ end
18
+
19
+ def handle_response_error
20
+ response = yield
21
+ { raw_response: response, success: true }
22
+ rescue RestClient::RequestFailed => e
23
+ raise e unless e.response
24
+
25
+ { raw_response: e.response, success: false }
26
+ end
27
+
28
+ def parse_response(raw_response:, success:)
29
+ {
30
+ success: success,
31
+ status: raw_response.code,
32
+ body: raw_response.body,
33
+ headers: raw_response.headers
34
+ }
35
+ end
36
+
37
+ def maybe_log_result(result, logger:)
38
+ logger && logger << "# => Response: #{result}\n"
39
+ end
40
+
41
+ def parse_json(json)
42
+ JSON.parse(json)
43
+ rescue JSON::ParserError
44
+ json
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,28 +1,10 @@
1
- require 'webmock'
2
- require 'rest_api_builder/url_helper'
1
+ require 'forwardable'
2
+ require 'rest_api_builder/webmock_request_expectations/expectations'
3
3
 
4
4
  module RestAPIBuilder
5
- class WebMockRequestExpectationsSingleton
6
- include WebMock::API
7
- include RestAPIBuilder::UrlHelper
5
+ module WebMockRequestExpectations
6
+ extend Forwardable
8
7
 
9
- def expect_json_execute(response: nil, **options)
10
- if response && response[:body]
11
- response = response.merge(body: JSON.generate(response[:body]))
12
- end
13
-
14
- expect_execute(**options, response: response)
15
- end
16
-
17
- def expect_execute(base_url:, method:, path: nil, request: nil, response: nil)
18
- expectation = stub_request(method, full_url(base_url, path))
19
-
20
- expectation.with(request) if request
21
- expectation.to_return(response) if response
22
-
23
- expectation
24
- end
8
+ def_delegators Expectations, :expect_json_execute, :expect_execute
25
9
  end
26
-
27
- WebMockRequestExpectations = WebMockRequestExpectationsSingleton.new
28
10
  end
@@ -0,0 +1,47 @@
1
+ require 'webmock'
2
+ require 'rest_api_builder/helpers/url_helper'
3
+
4
+ module RestAPIBuilder
5
+ module WebMockRequestExpectations
6
+ module Expectations
7
+ extend WebMock::API
8
+ extend RestAPIBuilder::Helpers::UrlHelper
9
+
10
+ module_function
11
+
12
+ def expect_json_execute(response: nil, **options)
13
+ if response && response[:body]
14
+ response = response.merge(body: JSON.generate(response[:body]))
15
+ end
16
+
17
+ expect_execute(**options, response: response)
18
+ end
19
+
20
+ def expect_execute(base_url:, method:, path: nil, request: nil, response: nil)
21
+ url = path.is_a?(Regexp) ? /#{base_url}#{path}/ : full_url(base_url, path)
22
+ expectation = stub_request(method, url)
23
+
24
+ if !request.nil? && request.any?
25
+ add_request_expectations(expectation, request)
26
+ end
27
+
28
+ expectation.to_return(response) if response
29
+
30
+ expectation
31
+ end
32
+
33
+ def add_request_expectations(expectation, request)
34
+ if request[:body].is_a?(Hash)
35
+ request = request.merge(body: hash_including(request[:body]))
36
+ end
37
+
38
+ if request[:query].is_a?(Hash)
39
+ query = request[:query].transform_values(&:to_s)
40
+ request = request.merge(query: hash_including(query))
41
+ end
42
+
43
+ expectation.with(request)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'rest_api_builder'
3
- s.version = '0.0.6'
3
+ s.version = '0.2.0'
4
4
  s.summary = "A simple wrapper for rest-client"
5
5
  s.description = "A simple wrapper for rest-client aiming to make creation and testing of API clients easier."
6
6
  s.authors = ["Alexey D"]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rest_api_builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexey D
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-29 00:00:00.000000000 Z
11
+ date: 2020-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -62,11 +62,14 @@ files:
62
62
  - LICENSE
63
63
  - README.md
64
64
  - lib/rest_api_builder.rb
65
+ - lib/rest_api_builder/api_client.rb
66
+ - lib/rest_api_builder/helpers/string_helper.rb
67
+ - lib/rest_api_builder/helpers/url_helper.rb
65
68
  - lib/rest_api_builder/request.rb
66
- - lib/rest_api_builder/request_options.rb
67
- - lib/rest_api_builder/response_handler.rb
68
- - lib/rest_api_builder/url_helper.rb
69
+ - lib/rest_api_builder/request/request_options.rb
70
+ - lib/rest_api_builder/request/response_handler.rb
69
71
  - lib/rest_api_builder/webmock_request_expectations.rb
72
+ - lib/rest_api_builder/webmock_request_expectations/expectations.rb
70
73
  - rest_api_builder.gemspec
71
74
  homepage: https://github.com/alexeyds/rest_api_builder
72
75
  licenses:
@@ -87,8 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
90
  - !ruby/object:Gem::Version
88
91
  version: '0'
89
92
  requirements: []
90
- rubyforge_project:
91
- rubygems_version: 2.7.8
93
+ rubygems_version: 3.1.2
92
94
  signing_key:
93
95
  specification_version: 4
94
96
  summary: A simple wrapper for rest-client
@@ -1,33 +0,0 @@
1
- require 'rest_api_builder/url_helper'
2
-
3
- module RestAPIBuilder
4
- class RequestOptions
5
- include RestAPIBuilder::UrlHelper
6
-
7
- def compose(base_url:, method:, path: nil, body: nil, headers: {}, query: nil)
8
- if method == :get && body
9
- raise ArgumentError, 'GET requests do not support body'
10
- end
11
-
12
- headers = headers.merge(params: query) if query
13
-
14
- {
15
- method: method,
16
- url: full_url(base_url, path),
17
- payload: body,
18
- headers: headers
19
- }
20
- end
21
-
22
- def compose_json(**options)
23
- result = compose(**options)
24
- headers = result[:headers]
25
- payload = result[:payload]
26
-
27
- result.merge(
28
- headers: headers.merge(content_type: :json),
29
- payload: payload && JSON.generate(payload)
30
- )
31
- end
32
- end
33
- end
@@ -1,44 +0,0 @@
1
- module RestAPIBuilder
2
- class ResponseHandler
3
- def handle_json_response(**options, &block)
4
- result = handle_response(**options, &block)
5
- result.merge(body: parse_json(result[:body]))
6
- end
7
-
8
- def handle_response(logger: nil, &block)
9
- result = parse_response(**handle_response_error(&block))
10
- maybe_log_result(result, logger: logger)
11
- result
12
- end
13
-
14
- def handle_response_error
15
- response = yield
16
- { raw_response: response, success: true }
17
- rescue RestClient::RequestFailed => e
18
- raise e unless e.response
19
-
20
- { raw_response: e.response, success: false }
21
- end
22
-
23
- private
24
-
25
- def parse_response(raw_response:, success:)
26
- {
27
- success: success,
28
- status: raw_response.code,
29
- body: raw_response.body,
30
- headers: raw_response.headers
31
- }
32
- end
33
-
34
- def maybe_log_result(result, logger:)
35
- logger && logger << "# => Response: #{result}\n"
36
- end
37
-
38
- def parse_json(json)
39
- JSON.parse(json)
40
- rescue JSON::ParserError
41
- json
42
- end
43
- end
44
- end
@@ -1,11 +0,0 @@
1
- module RestAPIBuilder
2
- module UrlHelper
3
- def full_url(url, path)
4
- if path
5
- File.join(url, path)
6
- else
7
- url
8
- end
9
- end
10
- end
11
- end