rest_api_builder 0.0.2 → 0.1.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: cf07f7e26da929aa46e970e076dc189a744fafcb7cac4ccdc532529d3e6b8dd4
4
- data.tar.gz: 192ffc62ea57897d606260b0d255722ce23c624be85dc137efa411a6df77c995
3
+ metadata.gz: 2e0a07766b5d80c9e2913f70d5004cf946a3c01eb08ea9f43f4c36961122f441
4
+ data.tar.gz: 7be95767413d7fa4ce3f881282ff75b1c575353ef9fe5522e85c6936eac5d303
5
5
  SHA512:
6
- metadata.gz: b28c914cdb039919614d82e108d1413c349e9530a659ed78898b2cbe4e6b182a3ebdc3aa34f5b4383a21d68d827c9e7d7aef93fea97656536cde1197d6815500
7
- data.tar.gz: 1560932fb31df12f946b56060a5411e249d19926c287fb561c95e863af1d6be37ff106ce304b6eb8dc34dc7b620ecddb2fd0c2fd7a667a7598cc73e7f33071d1
6
+ metadata.gz: 45531bd762b6039b93f63306be50ada5b880099e04f1c9c59ffbbbca457b790fc2b3f032899b9dfdd65b469e7be319b052d8b024f035d2f0dd82e5e44dc96a1a
7
+ data.tar.gz: b75b1bfc01db61ebd28bc50200a0ec3c18f487819515e9cf558db7f4411fdd4a58d5039addaab209ba2896ba48160e2439a0f9fb6c4aa90c0b6af32780bd02f4
data/LICENSE CHANGED
File without changes
data/README.md CHANGED
@@ -8,7 +8,7 @@ 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's tries to solve these and similar issues by providing a set of helper methods to improve on [rest-client](https://github.com/rest-client/rest-client) features and an optional [webmock](https://github.com/bblimke/webmock) testing interface for it.
12
12
 
13
13
  ## Installation
14
14
  ```
@@ -30,7 +30,7 @@ Simply require webmock interface before your test, for example in your `test_hel
30
30
  describe 'my test' do
31
31
  it 'performs a request' do
32
32
  RestAPIBuilder::WebMockRequestExpectations.expect_execute(...).to_return(body: "hi!")
33
- result = RestAPIBuilder::Request.execute(...)
33
+ result = RestClient::Request.execute(...)
34
34
 
35
35
  # some assertions
36
36
  end
@@ -43,23 +43,39 @@ Simply require webmock interface before your test, for example in your `test_hel
43
43
  ```rb
44
44
  require "rest_api_builder"
45
45
 
46
- Request = RestAPIBuilder::Request
46
+ class MyRequest
47
+ include RestAPIBuilder
48
+
49
+ def execute(options)
50
+ handle_response do
51
+ RestClient::Request.execute(compose_request_options(**options))
52
+ end
53
+ end
54
+
55
+ def json_execute(options)
56
+ handle_json_response do
57
+ RestClient::Request.execute(compose_json_request_options(**options))
58
+ end
59
+ end
60
+ end
61
+
62
+ my_request = MyRequest.new
47
63
 
48
64
  # Simple request:
49
- response = Request.execute(base_url: "example.com", method: :get)
65
+ response = my_request.execute(base_url: "example.com", method: :get)
50
66
  response[:success] #=> true
51
67
  response[:status] #=> 200
52
68
  response[:body] #=> "<!doctype html>\n<html>..."
53
69
  response[:headers] #=> {:accept_ranges=>"bytes", ...}
54
70
 
55
71
  # Non-200 responses:
56
- response = Request.execute(base_url: "example.com", path: "/foo", method: :get)
72
+ response = my_request.execute(base_url: "example.com", path: "/foo", method: :get)
57
73
  response[:success] #=> false
58
74
  response[:status] #=> 404
59
75
  response[:body] #=> "<!doctype html>\n<html>..."
60
76
 
61
77
  # JSON requests:
62
- response = Request.json_execute(base_url: "api.github.com", path: "/users/octocat/orgs", method: :get)
78
+ response = my_request.json_execute(base_url: "api.github.com", path: "/users/octocat/orgs", method: :get)
63
79
  response[:success] #=> true
64
80
  response[:body] #=> []
65
81
  ```
@@ -72,12 +88,29 @@ require "rest_api_builder/webmock_request_expectations"
72
88
 
73
89
  WebMock.disable_net_connect!
74
90
 
75
- Request = RestAPIBuilder::Request
91
+ class MyRequest
92
+ include RestAPIBuilder
93
+
94
+ def execute(options)
95
+ handle_response do
96
+ RestClient::Request.execute(compose_request_options(**options))
97
+ end
98
+ end
99
+
100
+ def json_execute(options)
101
+ handle_json_response do
102
+ RestClient::Request.execute(compose_json_request_options(**options))
103
+ end
104
+ end
105
+ end
106
+
107
+ my_request = MyRequest.new
108
+
76
109
  Expectations = RestAPIBuilder::WebMockRequestExpectations
77
110
 
78
111
  # Simple expectation
79
112
  Expectations.expect_execute(base_url: "test.com", method: :get)
80
- response = Request.execute(base_url: "test.com", method: :get)
113
+ response = my_request.execute(base_url: "test.com", method: :get)
81
114
 
82
115
  response[:success] #=> true
83
116
  response[:status] #=> 200
@@ -88,7 +121,7 @@ response[:headers] #=> {}
88
121
  Expectations
89
122
  .expect_execute(base_url: "test.com", method: :get)
90
123
  .to_return(status: 404, body: "not found")
91
- response = Request.execute(base_url: "test.com", method: :get)
124
+ response = my_request.execute(base_url: "test.com", method: :get)
92
125
 
93
126
  response[:success] #=> false
94
127
  response[:status] #=> 404
@@ -101,11 +134,11 @@ Expectations.expect_execute(
101
134
  response: { body: 'hello' },
102
135
  request: { body: WebMock::API.hash_including({foo: "bar"}) }
103
136
  )
104
- response = Request.json_execute(base_url: "test.com", method: :post, body: {foo: "bar"})
137
+ response = my_request.json_execute(base_url: "test.com", method: :post, body: {foo: "bar"})
105
138
  response[:success] #=> true
106
139
  response[:body] #=> 'hello'
107
140
 
108
- Request.json_execute(base_url: "test.com", method: :post, body: {bar: "baz"}) # => Raises WebMock::NetConnectNotAllowedError
141
+ my_request.json_execute(base_url: "test.com", method: :post, body: {bar: "baz"}) # => Raises WebMock::NetConnectNotAllowedError
109
142
 
110
143
  # Using #expect_json_execute
111
144
  Expectations.expect_json_execute(
@@ -113,17 +146,14 @@ Expectations.expect_json_execute(
113
146
  method: :get,
114
147
  response: { body: {hi: 'hello'} }
115
148
  )
116
- response = Request.execute(base_url: "test.com", method: :get)
149
+ response = my_request.execute(base_url: "test.com", method: :get)
117
150
  response[:success] #=> true
118
151
  response[:body] #=> "{\"hi\":\"hello\"}"
119
152
  ```
120
153
 
121
154
  ## 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
-
155
+ ### RestAPIBuilder#compose_request_options(options)
156
+ Composes request options that can be passed to `RestClient::Request.execute`.
127
157
  #### Options:
128
158
  * **base_url**: Base URL of the request. Required.
129
159
  * **method**: HTTP method of the request(e.g :get, :post, :patch). Required.
@@ -131,16 +161,26 @@ Does not throw on non-200 responses like RestClient does, but will throw on any
131
161
  * **body**: Request Body. Optional.
132
162
  * **headers**: Request Headers. Optional.
133
163
  * **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
- * **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.
137
164
 
138
- ### RestAPIBuilder::Request.json_execute(options)
139
- A convenience shortcut for `#execute` which will also:
165
+ ### RestAPIBuilder#compose_json_request_options(options)
166
+ Accepts same options as `compose_request_options` but will also:
140
167
  - Add `Content-Type: 'application/json'` to request `headers`
141
- - Convert request `body` to JSON
142
- - Set `parse_json` option to `true`
168
+ - Convert request `body` to JSON if it's present
169
+
170
+ ### RestAPIBuilder#handle_response(options, &block)
171
+ Executes given block, expecting to receive `RestClient::Response` as a result.\
172
+ Returns **plain ruby hash** with following keys: `:success`, `:status`, `:body`, `:headers`\
173
+ This will also gracefully handle non-200 responses, but will throw on any error without defined response(e.g server timeout)
174
+
175
+ #### Options:
176
+ * **logger**: A `Logger` instance. If provided, will log response details as RestClient wont do this by default. Optional
177
+
178
+ ### RestAPIBuilder#handle_json_response(options, &block)
179
+ Same as `#handle_response` but will also attempt to decode response `:body`, returning it as is if a parsing error occurrs
143
180
 
181
+ ### RestAPIBuilder#handle_response_error(options, &block)
182
+ Low-level API, you can use this method if you want to work with the RestClient's responses directly without any conversions(e.g when using `block_response` or `raw_response` options of RestClient). This will handle errors in the same way as `#handle_response` does, but will not do anything else.\
183
+ Returns ruby hash with `:success` and `:raw_response` keys.
144
184
 
145
185
  ## WebMockRequestExpectations API
146
186
  ### RestAPIBuilder::WebMockRequestExpectations.expect_execute(options)
@@ -154,7 +194,7 @@ Defines a request expectation using WebMock's `stub_request`. Returns an instanc
154
194
  * **response**: response details which will be passed to `WebMock::RequestStub#to_return` if provided. Optional
155
195
 
156
196
  ### RestAPIBuilder::WebMockRequestExpectations.expect_json_execute(options)
157
- A convenience shortcut for `#json_execute` which will convert `request[:body]` to JSON if it's provided
197
+ A convenience shortcut for `#json_execute` which will convert `request[:body]` to JSON if it's present
158
198
 
159
199
  ## License
160
200
  MIT
@@ -1 +1,32 @@
1
- require 'rest_api_builder/request'
1
+ require 'forwardable'
2
+ require 'rest_api_builder/request_options'
3
+ require 'rest_api_builder/response_handler'
4
+
5
+ module RestAPIBuilder
6
+ class APIHelpers
7
+ extend Forwardable
8
+
9
+ def initialize
10
+ @request_options = RequestOptions.new
11
+ @response_handler = ResponseHandler.new
12
+ end
13
+
14
+ def_delegator :@request_options, :compose, :compose_request_options
15
+ def_delegator :@request_options, :compose_json, :compose_json_request_options
16
+
17
+ def_delegators :@response_handler, :handle_response, :handle_json_response, :handle_response_error
18
+ end
19
+
20
+ extend Forwardable
21
+
22
+ def_delegators :rest_api_builder_helpers,
23
+ :compose_request_options,
24
+ :compose_json_request_options,
25
+ :handle_response,
26
+ :handle_json_response,
27
+ :handle_response_error
28
+
29
+ def rest_api_builder_helpers
30
+ APIHelpers.new
31
+ end
32
+ end
@@ -0,0 +1,33 @@
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
@@ -0,0 +1,46 @@
1
+ require 'rest-client'
2
+
3
+ module RestAPIBuilder
4
+ class ResponseHandler
5
+ def handle_json_response(**options, &block)
6
+ result = handle_response(**options, &block)
7
+ result.merge(body: parse_json(result[:body]))
8
+ end
9
+
10
+ def handle_response(logger: nil, &block)
11
+ result = parse_response(**handle_response_error(&block))
12
+ maybe_log_result(result, logger: logger)
13
+ result
14
+ end
15
+
16
+ def handle_response_error
17
+ response = yield
18
+ { raw_response: response, success: true }
19
+ rescue RestClient::RequestFailed => e
20
+ raise e unless e.response
21
+
22
+ { raw_response: e.response, success: false }
23
+ end
24
+
25
+ private
26
+
27
+ def parse_response(raw_response:, success:)
28
+ {
29
+ success: success,
30
+ status: raw_response.code,
31
+ body: raw_response.body,
32
+ headers: raw_response.headers
33
+ }
34
+ end
35
+
36
+ def maybe_log_result(result, logger:)
37
+ logger && logger << "# => Response: #{result}\n"
38
+ end
39
+
40
+ def parse_json(json)
41
+ JSON.parse(json)
42
+ rescue JSON::ParserError
43
+ json
44
+ end
45
+ end
46
+ end
File without changes
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'rest_api_builder'
3
- s.version = '0.0.2'
3
+ s.version = '0.1.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.2
4
+ version: 0.1.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-16 00:00:00.000000000 Z
11
+ date: 2020-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -62,8 +62,8 @@ files:
62
62
  - LICENSE
63
63
  - README.md
64
64
  - lib/rest_api_builder.rb
65
- - lib/rest_api_builder/request.rb
66
- - lib/rest_api_builder/rest_client_response_parser.rb
65
+ - lib/rest_api_builder/request_options.rb
66
+ - lib/rest_api_builder/response_handler.rb
67
67
  - lib/rest_api_builder/url_helper.rb
68
68
  - lib/rest_api_builder/webmock_request_expectations.rb
69
69
  - rest_api_builder.gemspec
@@ -86,7 +86,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
86
  - !ruby/object:Gem::Version
87
87
  version: '0'
88
88
  requirements: []
89
- rubygems_version: 3.1.2
89
+ rubyforge_project:
90
+ rubygems_version: 2.7.8
90
91
  signing_key:
91
92
  specification_version: 4
92
93
  summary: A simple wrapper for rest-client
@@ -1,52 +0,0 @@
1
- require 'rest-client'
2
- require 'rest_api_builder/url_helper'
3
- require 'rest_api_builder/rest_client_response_parser'
4
-
5
- module RestAPIBuilder
6
- class RequestSingleton
7
- include RestAPIBuilder::UrlHelper
8
-
9
- def json_execute(headers: {}, body: nil, **options)
10
- headers = headers.merge(content_type: :json)
11
- body &&= JSON.generate(body)
12
- execute(**options, parse_json: true, headers: headers, body: body)
13
- end
14
-
15
- def execute(
16
- base_url:,
17
- method:,
18
- body: nil,
19
- headers: {},
20
- query: nil,
21
- path: nil,
22
- logger: nil,
23
- parse_json: false,
24
- rest_client_options: {}
25
- )
26
- if method == :get && body
27
- raise ArgumentError, 'GET requests do not support body'
28
- end
29
-
30
- response_parser = RestAPIBuilder::RestClientResponseParser.new(logger: logger, parse_json: parse_json)
31
- headers = headers.merge(params: query) if query
32
-
33
- begin
34
- response = RestClient::Request.execute(
35
- method: method,
36
- url: full_url(base_url, path),
37
- payload: body,
38
- headers: headers,
39
- log: logger,
40
- **rest_client_options
41
- )
42
- response_parser.parse_response(response, success: true)
43
- rescue RestClient::RequestFailed => e
44
- raise e unless e.response
45
-
46
- response_parser.parse_response(e.response, success: false)
47
- end
48
- end
49
- end
50
-
51
- Request = RequestSingleton.new
52
- end
@@ -1,35 +0,0 @@
1
- require 'json'
2
-
3
- module RestAPIBuilder
4
- class RestClientResponseParser
5
- def initialize(logger:, parse_json:)
6
- @logger = logger
7
- @parse_json = parse_json
8
- end
9
-
10
- def parse_response(response, success:)
11
- body = @parse_json ? parse_json(response.body) : response.body
12
- result = {
13
- success: success,
14
- status: response.code,
15
- body: body,
16
- headers: response.headers
17
- }
18
- maybe_log_result(result)
19
-
20
- result
21
- end
22
-
23
- private
24
-
25
- def parse_json(json)
26
- JSON.parse(json)
27
- rescue JSON::ParserError
28
- json
29
- end
30
-
31
- def maybe_log_result(result)
32
- @logger && @logger << "# => Response: #{result}"
33
- end
34
- end
35
- end