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 +4 -4
- data/LICENSE +0 -0
- data/README.md +309 -124
- data/lib/rest_api_builder.rb +1 -19
- data/lib/rest_api_builder/api_client.rb +16 -0
- data/lib/rest_api_builder/helpers/string_helper.rb +15 -0
- data/lib/rest_api_builder/helpers/url_helper.rb +15 -0
- data/lib/rest_api_builder/request.rb +12 -40
- data/lib/rest_api_builder/request/request_options.rb +37 -0
- data/lib/rest_api_builder/request/response_handler.rb +48 -0
- data/lib/rest_api_builder/webmock_request_expectations.rb +5 -23
- data/lib/rest_api_builder/webmock_request_expectations/expectations.rb +47 -0
- data/rest_api_builder.gemspec +1 -1
- metadata +9 -7
- data/lib/rest_api_builder/request_options.rb +0 -33
- data/lib/rest_api_builder/response_handler.rb +0 -44
- data/lib/rest_api_builder/url_helper.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d192a166c3b73c990be9893ac5f08bfd601aed7c4cc9c8bd8419ce2fe20c32be
|
4
|
+
data.tar.gz: 9d9974f0d2451412333dec8979a8bcdd3268b7bba8d15f58d7ef9ce4ccb63f31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
##
|
19
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
213
|
+
### `#define_resource_shortcuts(resources, resources_scope:, init_with:)`
|
214
|
+
Dynamically defines attribute readers for given resources
|
26
215
|
|
27
|
-
|
28
|
-
|
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
|
-
|
31
|
-
|
32
|
-
RestAPIBuilder::WebMockRequestExpectations.expect_execute(...).to_return(body: "hi!")
|
33
|
-
result = RestAPIBuilder::Request.execute(...)
|
228
|
+
class APIClient
|
229
|
+
include RestAPIBuilder::APIClient
|
34
230
|
|
35
|
-
|
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
|
-
|
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
|
-
|
259
|
+
### `#expect_execute(options)`
|
260
|
+
Defines a request expectation using WebMock's `stub_request`.
|
43
261
|
```rb
|
44
|
-
require
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
response = Request.execute(
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
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
|
70
|
-
require
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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.
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
117
|
-
response
|
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
|
data/lib/rest_api_builder.rb
CHANGED
@@ -1,21 +1,3 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
require 'rest_api_builder/request'
|
3
|
-
require 'rest_api_builder/
|
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
|
@@ -1,46 +1,18 @@
|
|
1
|
-
require '
|
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
|
-
|
5
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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 '
|
2
|
-
require 'rest_api_builder/
|
1
|
+
require 'forwardable'
|
2
|
+
require 'rest_api_builder/webmock_request_expectations/expectations'
|
3
3
|
|
4
4
|
module RestAPIBuilder
|
5
|
-
|
6
|
-
|
7
|
-
include RestAPIBuilder::UrlHelper
|
5
|
+
module WebMockRequestExpectations
|
6
|
+
extend Forwardable
|
8
7
|
|
9
|
-
|
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
|
data/rest_api_builder.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'rest_api_builder'
|
3
|
-
s.version = '0.0
|
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
|
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-
|
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
|
-
|
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
|