api_client_builder 1.0.1 → 1.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 +4 -4
- data/.gitignore +3 -1
- data/.rubocop.yml +34 -0
- data/.travis.yml +14 -12
- data/LICENSE.txt +1 -1
- data/README.md +47 -50
- data/api_client_builder.gemspec +13 -12
- data/build.sh +16 -3
- data/lib/api_client_builder.rb +13 -9
- data/lib/api_client_builder/api_client.rb +1 -7
- data/lib/api_client_builder/get_collection_request.rb +2 -2
- data/lib/api_client_builder/get_item_request.rb +0 -2
- data/lib/api_client_builder/post_request.rb +0 -2
- data/lib/api_client_builder/put_request.rb +0 -2
- data/lib/api_client_builder/request.rb +6 -6
- data/lib/api_client_builder/url_generator.rb +3 -5
- data/lib/api_client_builder/version.rb +1 -1
- data/spec/lib/api_client_builder/api_client_spec.rb +3 -4
- data/spec/lib/api_client_builder/get_collection_request_spec.rb +7 -10
- data/spec/lib/api_client_builder/get_item_request_spec.rb +4 -6
- data/spec/lib/api_client_builder/post_request_spec.rb +3 -5
- data/spec/lib/api_client_builder/put_request_spec.rb +3 -5
- data/spec/lib/api_client_builder/request_spec.rb +7 -9
- data/spec/lib/api_client_builder/response_spec.rb +1 -2
- data/spec/lib/api_client_builder/test_client/client.rb +2 -3
- data/spec/lib/api_client_builder/test_client/http_client_handler.rb +1 -2
- data/spec/lib/api_client_builder/test_client/response_handler.rb +9 -14
- data/spec/lib/api_client_builder/url_generator_spec.rb +8 -7
- data/spec/spec_helper.rb +8 -2
- metadata +35 -22
- data/.dockerignore +0 -1
- data/Dockerfile +0 -16
- data/api_client_builder-1.0.0.gem +0 -0
- data/docker-compose.yml +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8fd053293f5d2e0c724f2c149c68a814099179e9
|
4
|
+
data.tar.gz: 28db90774bea6b541e632e74e578b933d3a92e54
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e693b8c048a88dcd3e68332817afff076a684329b699588bac2706a450510a4c4ad7d32669c344f9fb59b0784562f4612efa84c6befa062416417eb9629b13b8
|
7
|
+
data.tar.gz: c813e8124cdaaa0869ae53ee613aa97df839740b883a1a86d0076f8bd2e60cde29c0a5dbf812a12bce8d57982c10cc6dfb4b7af28e9104e76ad3a21190e1d277
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.3
|
3
|
+
|
4
|
+
Metrics/BlockLength:
|
5
|
+
Exclude:
|
6
|
+
- spec/**/*.rb
|
7
|
+
|
8
|
+
Metrics/LineLength:
|
9
|
+
Max: 120 # Default: 80
|
10
|
+
|
11
|
+
Metrics/MethodLength:
|
12
|
+
Max: 20 # Default: 10
|
13
|
+
|
14
|
+
Naming/AccessorMethodName: # TODO: Enable and fix API at some point.
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Style/Documentation:
|
18
|
+
# This cop checks for missing top-level documentation of classes and modules.
|
19
|
+
# Classes with no body and namespace modules are exempt from the check.
|
20
|
+
# Namespace modules are modules that have nothing in their bodies except
|
21
|
+
# classes or other modules.
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Style/FrozenStringLiteralComment:
|
25
|
+
# `when_needed` will add the frozen string literal comment to files
|
26
|
+
# only when the `TargetRubyVersion` is set to 2.3+.
|
27
|
+
# `always` will always add the frozen string literal comment to a file
|
28
|
+
# regardless of the Ruby version or if `freeze` or `<<` are called on a
|
29
|
+
# string literal. If you run code against multiple versions of Ruby, it is
|
30
|
+
# possible that this will create errors in Ruby 2.3.0+.
|
31
|
+
#
|
32
|
+
# See: https://wyeworks.com/blog/2015/12/1/immutable-strings-in-ruby-2-dot-3
|
33
|
+
EnforcedStyle: when_needed
|
34
|
+
Enabled: false
|
data/.travis.yml
CHANGED
@@ -1,18 +1,20 @@
|
|
1
|
-
|
1
|
+
dist: trusty
|
2
2
|
sudo: false
|
3
|
+
language: ruby
|
3
4
|
cache: bundler
|
5
|
+
|
4
6
|
rvm:
|
5
|
-
# - ruby-head
|
6
|
-
- 2.1
|
7
|
-
- 2.2
|
8
7
|
- 2.3
|
8
|
+
- 2.4
|
9
|
+
- 2.5
|
10
|
+
|
9
11
|
matrix:
|
10
12
|
fast_finish: true
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
|
14
|
+
before_install: gem update bundler
|
15
|
+
bundler_args: --jobs 3
|
16
|
+
install: bundle install --jobs 3
|
17
|
+
|
18
|
+
script:
|
19
|
+
- bundle exec rubocop --fail-level autocorrect
|
20
|
+
- bundle exec rspec
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,9 @@
|
|
3
3
|
API Client Builder was created to reduce the overhead of creating API clients.
|
4
4
|
|
5
5
|
It provides a DSL for defining endpoints and only requires you to define handlers
|
6
|
-
for
|
6
|
+
for HTTP requests and responses.
|
7
|
+
|
8
|
+
[](https://travis-ci.org/instructure/api-client-builder)
|
7
9
|
|
8
10
|
---
|
9
11
|
|
@@ -28,10 +30,6 @@ Or install it yourself as:
|
|
28
30
|
The basic client structure looks like this.
|
29
31
|
|
30
32
|
```ruby
|
31
|
-
require 'api_client_builder/api_client'
|
32
|
-
require 'path/to/your/http_client_handler'
|
33
|
-
require 'path/to/your/response_handler'
|
34
|
-
|
35
33
|
class Client < APIClientBulder::APIClient
|
36
34
|
def initialize(**opts)
|
37
35
|
super(domain: opts[:domain],
|
@@ -55,8 +53,8 @@ def response_handler_build(http_client, start_url, type)
|
|
55
53
|
end
|
56
54
|
```
|
57
55
|
|
58
|
-
|
59
56
|
### Defining routes on the client
|
57
|
+
|
60
58
|
To define routes on the api client, use the DSL provided by the
|
61
59
|
builder's APIClient class. Four parts have been defined to help:
|
62
60
|
|
@@ -81,7 +79,6 @@ builder's APIClient class. Four parts have been defined to help:
|
|
81
79
|
- Note that any symbols in the route will be interpolated as required
|
82
80
|
params when calling the method on the client.
|
83
81
|
|
84
|
-
|
85
82
|
---
|
86
83
|
|
87
84
|
## Route Examples
|
@@ -91,7 +88,7 @@ builder's APIClient class. Four parts have been defined to help:
|
|
91
88
|
Define the route on the client
|
92
89
|
|
93
90
|
```ruby
|
94
|
-
|
91
|
+
get :some_object, :singular, 'some_objects/:id'
|
95
92
|
```
|
96
93
|
|
97
94
|
Use the defined route
|
@@ -107,7 +104,7 @@ response_body = single_request.response
|
|
107
104
|
Define the route on the client
|
108
105
|
|
109
106
|
```ruby
|
110
|
-
|
107
|
+
get :some_objects, :collection, 'some_objects'
|
111
108
|
```
|
112
109
|
|
113
110
|
Use the defined route
|
@@ -116,7 +113,7 @@ Use the defined route
|
|
116
113
|
collection_request = client.get_some_objects
|
117
114
|
|
118
115
|
collection_request.each do |item|
|
119
|
-
#Item will be a Hash if you use the default response in the response handler
|
116
|
+
# Item will be a Hash if you use the default response in the response handler
|
120
117
|
end
|
121
118
|
```
|
122
119
|
|
@@ -166,16 +163,16 @@ get :some_objects_for_course, :collection, 'course/:course_id/some_objects'
|
|
166
163
|
|
167
164
|
## Defining an HTTP Client Handler
|
168
165
|
|
169
|
-
The HTTP Client Handler is designed to manage the
|
166
|
+
The HTTP Client Handler is designed to manage the HTTP requests themselves. Since
|
170
167
|
actually making an HTTP request typically requires some amount of authentication,
|
171
168
|
it is suggested that authentication and headers are managed here as well.
|
172
169
|
|
173
|
-
The
|
170
|
+
The HTTP client handler requires '#get', '#post', and '#put' to be defined here
|
174
171
|
with the shown method signature.
|
175
172
|
|
176
173
|
```ruby
|
177
174
|
class HTTPClientHandler
|
178
|
-
#Do initialization here, generally authentication creds and a domain is sent in
|
175
|
+
# Do initialization here, generally authentication creds and a domain is sent in
|
179
176
|
|
180
177
|
def get(route, params = nil, headers = {})
|
181
178
|
client.get(route, params, headers)
|
@@ -189,9 +186,9 @@ class HTTPClientHandler
|
|
189
186
|
client.post(route, params, headers)
|
190
187
|
end
|
191
188
|
|
192
|
-
#Define a client to use here. The HTTPClient gem is a good option
|
189
|
+
# Define a client to use here. The HTTPClient gem is a good option
|
193
190
|
|
194
|
-
#Build up headers and authentication handling here as well
|
191
|
+
# Build up headers and authentication handling here as well
|
195
192
|
end
|
196
193
|
```
|
197
194
|
|
@@ -219,11 +216,11 @@ these actions simpler.
|
|
219
216
|
|
220
217
|
```ruby
|
221
218
|
class ResponseHandler
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
219
|
+
def initialize(http_client_handler, start_url, type)
|
220
|
+
@http_client = http_client_handler
|
221
|
+
@start_url = start_url
|
222
|
+
@type = type
|
223
|
+
end
|
227
224
|
end
|
228
225
|
```
|
229
226
|
|
@@ -239,13 +236,13 @@ of pages and also start the page counter.
|
|
239
236
|
|
240
237
|
```ruby
|
241
238
|
def get_first_page
|
242
|
-
#Build the URL -- this could be to add pagination params to the route, or
|
243
|
-
#
|
239
|
+
# Build the URL -- this could be to add pagination params to the route, or
|
240
|
+
# add whatever else is necessary to the route.
|
244
241
|
http_response = @http_client.get("a URL")
|
245
242
|
|
246
|
-
#Generally the first page will contain information about how many pages a
|
247
|
-
#
|
248
|
-
#Be sure to set the current page count as well
|
243
|
+
# Generally the first page will contain information about how many pages a
|
244
|
+
# paginated response will have. Set that here: `@max_pages`
|
245
|
+
# Be sure to set the current page count as well: `@current_page`
|
249
246
|
build_response(http_response)
|
250
247
|
end
|
251
248
|
```
|
@@ -258,17 +255,16 @@ return a boolean denoting the presence of more pages.
|
|
258
255
|
|
259
256
|
```ruby
|
260
257
|
def get_next_page
|
261
|
-
#Build the URL -- this could be to add pagination params to the route, or
|
262
|
-
#
|
258
|
+
# Build the URL -- this could be to add pagination params to the route, or
|
259
|
+
# add whatever else is necessary to the route:
|
263
260
|
http_response = @http_client.get("a URL")
|
264
261
|
|
265
|
-
#If the http_response is valid then increment the page counter here
|
262
|
+
# If the http_response is valid then increment the page counter here.
|
266
263
|
build_response(http_response)
|
267
264
|
end
|
268
265
|
|
269
266
|
def more_pages?
|
270
|
-
|
271
|
-
return true
|
267
|
+
@current_page < @max_pages
|
272
268
|
end
|
273
269
|
```
|
274
270
|
|
@@ -278,9 +274,9 @@ The builder will call `#put_request` when handling put routes.
|
|
278
274
|
|
279
275
|
```ruby
|
280
276
|
def put_request
|
281
|
-
#Build the URL -- this could be to add pagination params to the route, or
|
282
|
-
#
|
283
|
-
#Also send the body if thats how the client handler is configured
|
277
|
+
# Build the URL -- this could be to add pagination params to the route, or
|
278
|
+
# add whatever else is necessary to the route.
|
279
|
+
# Also send the body if thats how the client handler is configured.
|
284
280
|
http_response = @http_client.put("a URL", {})
|
285
281
|
build_response(http_response)
|
286
282
|
end
|
@@ -292,9 +288,9 @@ The builder will call `#post_request` when handling post routes.
|
|
292
288
|
|
293
289
|
```ruby
|
294
290
|
def post_request
|
295
|
-
#Build the URL -- this could be to add pagination params to the route, or
|
296
|
-
#
|
297
|
-
#Also send the body if
|
291
|
+
# Build the URL -- this could be to add pagination params to the route, or
|
292
|
+
# add whatever else is necessary to the route.
|
293
|
+
# Also send the body if that's how the client handler is configured.
|
298
294
|
http_response = @http_client.post("a URL", {})
|
299
295
|
build_response(http_response)
|
300
296
|
end
|
@@ -308,34 +304,35 @@ the following methods.
|
|
308
304
|
```ruby
|
309
305
|
def retryable?(status_code)
|
310
306
|
if @opts[:exponential_backoff]
|
311
|
-
#Define the conditions of whether or not the provided status code is retry-able
|
307
|
+
# Define the conditions of whether or not the provided status code is retry-able
|
308
|
+
true
|
312
309
|
else
|
313
|
-
|
310
|
+
false
|
314
311
|
end
|
315
312
|
end
|
316
313
|
|
317
314
|
def reset_retries
|
318
|
-
#Track the number of retries so the request is not retried indefinitely.
|
319
|
-
#
|
320
|
-
#
|
315
|
+
# Track the number of retries so the request is not retried indefinitely.
|
316
|
+
# The builder will reset them when it no longer is retrying by calling this
|
317
|
+
# method.
|
321
318
|
@retries = 0
|
322
319
|
end
|
323
320
|
|
324
321
|
def retry_request
|
325
|
-
#Increment the retries here so the request is not retried indefinitely.
|
322
|
+
# Increment the retries here so the request is not retried indefinitely.
|
326
323
|
@retries += 1
|
327
324
|
|
328
|
-
#Build the URL -- this could be to add pagination params to the route, or
|
329
|
-
#
|
325
|
+
# Build the URL -- this could be to add pagination params to the route, or
|
326
|
+
# add whatever else is necessary to the route.
|
330
327
|
response = @http_client.the_action_to_retry("a URL")
|
331
328
|
build_response(response)
|
332
329
|
end
|
333
330
|
```
|
334
331
|
|
335
|
-
#### Managing the
|
332
|
+
#### Managing the HTTP response
|
336
333
|
|
337
334
|
The builder defines a default `Response` object that will provide the minimally
|
338
|
-
required interface for managing an
|
335
|
+
required interface for managing an HTTP response.
|
339
336
|
|
340
337
|
```ruby
|
341
338
|
def build_response(http_response)
|
@@ -373,10 +370,10 @@ is not a "success."
|
|
373
370
|
single_request = client.get_some_object(id: 123)
|
374
371
|
|
375
372
|
single_request.on_error do |page, handler|
|
376
|
-
#The page will have all of the status information
|
377
|
-
#The handler is the defined response_handler
|
378
|
-
#Use either to glean more information about why the request was an error and
|
379
|
-
# handle the error here
|
373
|
+
# The page will have all of the status information.
|
374
|
+
# The handler is the defined response_handler.
|
375
|
+
# Use either to glean more information about why the request was an error and
|
376
|
+
# handle the error here.
|
380
377
|
end
|
381
378
|
|
382
379
|
response_body = single_request.response
|
data/api_client_builder.gemspec
CHANGED
@@ -1,24 +1,25 @@
|
|
1
|
-
|
2
|
-
require File.expand_path('../lib/api_client_builder/version', __FILE__)
|
1
|
+
require File.expand_path('lib/api_client_builder/version', __dir__)
|
3
2
|
|
4
3
|
Gem::Specification.new do |gem|
|
5
|
-
gem.name =
|
6
|
-
gem.summary =
|
7
|
-
gem.description =
|
8
|
-
gem.authors = ['Jayce Higgins']
|
9
|
-
gem.email = ['jhiggins@instructure.com', 'eng@instructure.com']
|
4
|
+
gem.name = 'api_client_builder'
|
5
|
+
gem.summary = 'API Client Builder provides an easy to use interface for creating HTTP api clients'
|
6
|
+
gem.description = 'API Client Builder provides an easy to use interface for creating HTTP api clients'
|
7
|
+
gem.authors = ['Jayce Higgins', 'Bryan Petty']
|
8
|
+
gem.email = ['jhiggins@instructure.com', 'bpetty@instructure.com', 'eng@instructure.com']
|
9
|
+
gem.homepage = 'https://github.com/instructure/api-client-builder'
|
10
10
|
|
11
11
|
gem.version = APIClientBuilder::VERSION
|
12
|
-
gem.required_ruby_version = '>= 2.
|
12
|
+
gem.required_ruby_version = '>= 2.3'
|
13
13
|
|
14
|
-
gem.license
|
14
|
+
gem.license = 'MIT'
|
15
15
|
|
16
16
|
gem.add_development_dependency 'pry'
|
17
|
-
gem.add_development_dependency 'rspec'
|
18
|
-
gem.add_development_dependency '
|
17
|
+
gem.add_development_dependency 'rspec', '~> 3.7'
|
18
|
+
gem.add_development_dependency 'rubocop', '~> 0.57.2'
|
19
|
+
gem.add_development_dependency 'simplecov', '~> 0'
|
19
20
|
|
20
21
|
gem.files = `git ls-files`.split("\n")
|
21
22
|
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
23
|
gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
23
|
-
gem.require_paths = [
|
24
|
+
gem.require_paths = ['lib']
|
24
25
|
end
|
data/build.sh
CHANGED
@@ -1,4 +1,17 @@
|
|
1
|
-
#!/bin/bash
|
1
|
+
#!/usr/bin/env bash
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
set -e
|
4
|
+
|
5
|
+
docker pull ruby:2.3
|
6
|
+
docker pull ruby:2.4
|
7
|
+
docker pull ruby:2.5
|
8
|
+
|
9
|
+
docker run --rm -v "`pwd`:/app" -w /app --user `id -u`:`id -g` -e HOME="/tmp" "ruby:2.3" \
|
10
|
+
/bin/sh -c "echo \"gem: --no-document\" >> ~/.gemrc && bundle install --jobs 5 --quiet && bundle exec rubocop --cache false --fail-level autocorrect"
|
11
|
+
|
12
|
+
for version in '2.3' '2.4' '2.5'; do
|
13
|
+
echo "Testing Ruby $version..."
|
14
|
+
docker run --rm -v "`pwd`:/app" -w /app --user `id -u`:`id -g` \
|
15
|
+
-e HOME="/tmp" "ruby:$version" /bin/sh -c \
|
16
|
+
"echo \"gem: --no-document\" >> ~/.gemrc && bundle install --jobs 5 --quiet && bundle exec rspec"
|
17
|
+
done
|
data/lib/api_client_builder.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
require_relative 'api_client_builder/version'
|
4
|
+
require_relative 'api_client_builder/request'
|
5
|
+
require_relative 'api_client_builder/response'
|
6
|
+
|
7
|
+
require_relative 'api_client_builder/get_collection_request'
|
8
|
+
require_relative 'api_client_builder/get_item_request'
|
9
|
+
require_relative 'api_client_builder/post_request'
|
10
|
+
require_relative 'api_client_builder/put_request'
|
11
|
+
require_relative 'api_client_builder/url_generator'
|
12
|
+
|
13
|
+
require_relative 'api_client_builder/api_client'
|
@@ -1,9 +1,3 @@
|
|
1
|
-
require 'api_client_builder/get_collection_request'
|
2
|
-
require 'api_client_builder/get_item_request'
|
3
|
-
require 'api_client_builder/post_request'
|
4
|
-
require 'api_client_builder/put_request'
|
5
|
-
require 'api_client_builder/url_generator'
|
6
|
-
|
7
1
|
module APIClientBuilder
|
8
2
|
# The base APIClient that defines the interface for defining an API Client.
|
9
3
|
# Should be sub-classed and then provided an HTTPClient handler and a
|
@@ -28,7 +22,7 @@ module APIClientBuilder
|
|
28
22
|
# @param route [String] defines the routes endpoint
|
29
23
|
#
|
30
24
|
# @return [Request] either a GetCollection or GetItem request
|
31
|
-
def self.get(type, plurality, route, **
|
25
|
+
def self.get(type, plurality, route, **_opts)
|
32
26
|
if plurality == :collection
|
33
27
|
define_method("get_#{type}") do |**params|
|
34
28
|
GetCollectionRequest.new(
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'api_client_builder/request'
|
2
|
-
|
3
1
|
module APIClientBuilder
|
4
2
|
# The multi item response object to be used as the container for
|
5
3
|
# collection responses from the defined API
|
@@ -11,6 +9,7 @@ module APIClientBuilder
|
|
11
9
|
# strategy is defined concretely on the response handler.
|
12
10
|
#
|
13
11
|
# @return [JSON] the http response body
|
12
|
+
# rubocop:disable Metrics/AbcSize
|
14
13
|
def each
|
15
14
|
if block_given?
|
16
15
|
each_page do |page|
|
@@ -32,6 +31,7 @@ module APIClientBuilder
|
|
32
31
|
Enumerator.new(self, :each)
|
33
32
|
end
|
34
33
|
end
|
34
|
+
# rubocop:enable Metrics/AbcSize
|
35
35
|
|
36
36
|
private
|
37
37
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module APIClientBuilder
|
2
|
-
class DefaultPageError < StandardError;end
|
2
|
+
class DefaultPageError < StandardError; end
|
3
3
|
|
4
4
|
class Request
|
5
5
|
attr_reader :type, :response_handler, :body, :error_handlers_collection
|
@@ -23,11 +23,11 @@ module APIClientBuilder
|
|
23
23
|
# @return [Array<Block>] the error handlers collection
|
24
24
|
def error_handlers
|
25
25
|
if error_handlers_collection.empty?
|
26
|
-
|
27
|
-
raise DefaultPageError,
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
on_error do |page, _handler|
|
27
|
+
raise DefaultPageError, <<~MESSAGE
|
28
|
+
Default error for bad response. If you want to handle this error use #on_error
|
29
|
+
on the response in your api consumer. Error Code: #{page.status_code}.
|
30
|
+
MESSAGE
|
31
31
|
end
|
32
32
|
end
|
33
33
|
error_handlers_collection
|
@@ -1,7 +1,5 @@
|
|
1
|
-
require 'uri'
|
2
|
-
|
3
1
|
module APIClientBuilder
|
4
|
-
class NoURLError < StandardError;end
|
2
|
+
class NoURLError < StandardError; end
|
5
3
|
class URLGenerator
|
6
4
|
# Receives a domain and parses it into a URI
|
7
5
|
#
|
@@ -23,8 +21,8 @@ module APIClientBuilder
|
|
23
21
|
#
|
24
22
|
# @return [URI] the fully built route
|
25
23
|
def build_route(route, **params)
|
26
|
-
string_params = route.split('/').select{|param| param.start_with?(':')}
|
27
|
-
symboled_params = string_params.map{|param| param.tr(':', '').to_sym}
|
24
|
+
string_params = route.split('/').select { |param| param.start_with?(':') }
|
25
|
+
symboled_params = string_params.map { |param| param.tr(':', '').to_sym }
|
28
26
|
|
29
27
|
new_route = route.clone
|
30
28
|
symboled_params.each do |param|
|
@@ -1,11 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
|
3
|
-
require 'api_client_builder/api_client'
|
2
|
+
require_relative 'test_client/client'
|
4
3
|
|
5
4
|
module APIClientBuilder
|
6
5
|
describe APIClient do
|
7
|
-
let(:domain) {'https://www.domain.com/api/endpoints/'}
|
8
|
-
let(:client) {TestClient::Client.new(domain: domain)}
|
6
|
+
let(:domain) { 'https://www.domain.com/api/endpoints/' }
|
7
|
+
let(:client) { TestClient::Client.new(domain: domain) }
|
9
8
|
|
10
9
|
describe '.get' do
|
11
10
|
context 'plurality is :collection' do
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
|
3
|
-
require 'lib/api_client_builder/test_client/client'
|
2
|
+
require_relative 'test_client/client'
|
4
3
|
|
5
4
|
module APIClientBuilder
|
6
5
|
describe GetCollectionRequest do
|
@@ -21,9 +20,8 @@ module APIClientBuilder
|
|
21
20
|
bad_response = APIClientBuilder::Response.new('bad request', 500, [200])
|
22
21
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:get_first_page).and_return(bad_response)
|
23
22
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:retry_request).and_return(bad_response)
|
24
|
-
expect{ client.get_some_objects.each{} }.to raise_error(
|
25
|
-
APIClientBuilder::DefaultPageError,
|
26
|
-
"Default error for bad response. If you want to handle this error use #on_error on the response in your api consumer. Error Code: 500"
|
23
|
+
expect { client.get_some_objects.each {} }.to raise_error(
|
24
|
+
APIClientBuilder::DefaultPageError, /Error Code: 500/
|
27
25
|
)
|
28
26
|
end
|
29
27
|
|
@@ -32,7 +30,7 @@ module APIClientBuilder
|
|
32
30
|
client = TestClient::Client.new(domain: 'https://www.domain.com/api/endpoints/')
|
33
31
|
|
34
32
|
bad_response = APIClientBuilder::Response.new('bad request', 500, [200])
|
35
|
-
good_response = APIClientBuilder::Response.new([1,2,3], 200, [200])
|
33
|
+
good_response = APIClientBuilder::Response.new([1, 2, 3], 200, [200])
|
36
34
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:get_first_page).and_return(bad_response)
|
37
35
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:more_pages?).and_return(false)
|
38
36
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:retry_request).and_return(good_response)
|
@@ -47,13 +45,12 @@ module APIClientBuilder
|
|
47
45
|
client = TestClient::Client.new(domain: 'https://www.domain.com/api/endpoints/')
|
48
46
|
|
49
47
|
bad_response = APIClientBuilder::Response.new('bad request', 400, [200])
|
50
|
-
good_response = APIClientBuilder::Response.new([1,2,3], 200, [200])
|
48
|
+
good_response = APIClientBuilder::Response.new([1, 2, 3], 200, [200])
|
51
49
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:get_first_page).and_return(bad_response)
|
52
50
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:more_pages?).and_return(false)
|
53
51
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:retry_request).and_return(good_response)
|
54
|
-
expect{ client.get_some_objects.each{} }.to raise_error(
|
55
|
-
APIClientBuilder::DefaultPageError,
|
56
|
-
"Default error for bad response. If you want to handle this error use #on_error on the response in your api consumer. Error Code: 400"
|
52
|
+
expect { client.get_some_objects.each {} }.to raise_error(
|
53
|
+
APIClientBuilder::DefaultPageError, /Error Code: 400/
|
57
54
|
)
|
58
55
|
end
|
59
56
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
|
3
|
-
require 'lib/api_client_builder/test_client/client'
|
2
|
+
require_relative 'test_client/client'
|
4
3
|
|
5
4
|
module APIClientBuilder
|
6
5
|
describe GetItemRequest do
|
@@ -21,9 +20,8 @@ module APIClientBuilder
|
|
21
20
|
bad_response = APIClientBuilder::Response.new('bad request', 500, [200])
|
22
21
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:get_first_page).and_return(bad_response)
|
23
22
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:retry_request).and_return(bad_response)
|
24
|
-
expect{ client.get_some_object(some_id: '123').response }.to raise_error(
|
25
|
-
APIClientBuilder::DefaultPageError,
|
26
|
-
"Default error for bad response. If you want to handle this error use #on_error on the response in your api consumer. Error Code: 500"
|
23
|
+
expect { client.get_some_object(some_id: '123').response }.to raise_error(
|
24
|
+
APIClientBuilder::DefaultPageError, /Error Code: 500/
|
27
25
|
)
|
28
26
|
end
|
29
27
|
|
@@ -32,7 +30,7 @@ module APIClientBuilder
|
|
32
30
|
client = TestClient::Client.new(domain: 'https://www.domain.com/api/endpoints/')
|
33
31
|
|
34
32
|
bad_response = APIClientBuilder::Response.new('bad request', 500, [200])
|
35
|
-
good_response = APIClientBuilder::Response.new([1,2,3], 200, [200])
|
33
|
+
good_response = APIClientBuilder::Response.new([1, 2, 3], 200, [200])
|
36
34
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:get_first_page).and_return(bad_response)
|
37
35
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:more_pages?).and_return(false)
|
38
36
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:retry_request).and_return(good_response)
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
|
3
|
-
require 'lib/api_client_builder/test_client/client'
|
2
|
+
require_relative 'test_client/client'
|
4
3
|
|
5
4
|
module APIClientBuilder
|
6
5
|
describe PostRequest do
|
@@ -20,9 +19,8 @@ module APIClientBuilder
|
|
20
19
|
|
21
20
|
bad_response = APIClientBuilder::Response.new('bad request', 400, [200])
|
22
21
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:post_request).and_return(bad_response)
|
23
|
-
expect{ client.post_some_object({}).response }.to raise_error(
|
24
|
-
APIClientBuilder::DefaultPageError,
|
25
|
-
"Default error for bad response. If you want to handle this error use #on_error on the response in your api consumer. Error Code: 400"
|
22
|
+
expect { client.post_some_object({}).response }.to raise_error(
|
23
|
+
APIClientBuilder::DefaultPageError, /Error Code: 400/
|
26
24
|
)
|
27
25
|
end
|
28
26
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
|
3
|
-
require 'lib/api_client_builder/test_client/client'
|
2
|
+
require_relative 'test_client/client'
|
4
3
|
|
5
4
|
module APIClientBuilder
|
6
5
|
describe PutRequest do
|
@@ -20,9 +19,8 @@ module APIClientBuilder
|
|
20
19
|
|
21
20
|
bad_response = APIClientBuilder::Response.new('bad request', 400, [200])
|
22
21
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:put_request).and_return(bad_response)
|
23
|
-
expect{ client.put_some_object({}).response }.to raise_error(
|
24
|
-
APIClientBuilder::DefaultPageError,
|
25
|
-
"Default error for bad response. If you want to handle this error use #on_error on the response in your api consumer. Error Code: 400"
|
22
|
+
expect { client.put_some_object({}).response }.to raise_error(
|
23
|
+
APIClientBuilder::DefaultPageError, /Error Code: 400/
|
26
24
|
)
|
27
25
|
end
|
28
26
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
|
3
|
-
require 'api_client_builder/request'
|
2
|
+
require_relative 'test_client/client'
|
4
3
|
|
5
4
|
module APIClientBuilder
|
6
5
|
describe Request do
|
@@ -12,9 +11,8 @@ module APIClientBuilder
|
|
12
11
|
bad_response = APIClientBuilder::Response.new('bad request', 400, [200])
|
13
12
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:get_first_page).and_return(bad_response)
|
14
13
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:retry_request).and_return(bad_response)
|
15
|
-
expect{ client.get_some_object(some_id: '123').response }.to raise_error(
|
16
|
-
APIClientBuilder::DefaultPageError,
|
17
|
-
"Default error for bad response. If you want to handle this error use #on_error on the response in your api consumer. Error Code: 400"
|
14
|
+
expect { client.get_some_object(some_id: '123').response }.to raise_error(
|
15
|
+
APIClientBuilder::DefaultPageError, /Error Code: 400/
|
18
16
|
)
|
19
17
|
end
|
20
18
|
end
|
@@ -28,13 +26,13 @@ module APIClientBuilder
|
|
28
26
|
allow_any_instance_of(TestClient::ResponseHandler).to receive(:retry_request).and_return(bad_response)
|
29
27
|
object_response = client.get_some_object(some_id: '123')
|
30
28
|
|
31
|
-
object_response.on_error do |
|
32
|
-
raise StandardError,
|
29
|
+
object_response.on_error do |_page, _response|
|
30
|
+
raise StandardError, 'Something Bad Happened'
|
33
31
|
end
|
34
32
|
|
35
|
-
expect{object_response.response}.to raise_error(
|
33
|
+
expect { object_response.response }.to raise_error(
|
36
34
|
StandardError,
|
37
|
-
|
35
|
+
'Something Bad Happened'
|
38
36
|
)
|
39
37
|
end
|
40
38
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'api_client_builder/response'
|
3
2
|
|
4
3
|
module APIClientBuilder
|
5
4
|
describe Response do
|
@@ -18,7 +17,7 @@ module APIClientBuilder
|
|
18
17
|
|
19
18
|
it 'returns false when the response is otherwise marked as failed' do
|
20
19
|
page = Response.new([], 200, [200])
|
21
|
-
page.mark_failed
|
20
|
+
page.mark_failed 'Something happened'
|
22
21
|
|
23
22
|
expect(page.success?).to eq(false)
|
24
23
|
end
|
@@ -1,6 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'lib/api_client_builder/test_client/http_client_handler'
|
1
|
+
require_relative 'response_handler'
|
2
|
+
require_relative 'http_client_handler'
|
4
3
|
|
5
4
|
module TestClient
|
6
5
|
class Client < APIClientBuilder::APIClient
|
@@ -1,36 +1,33 @@
|
|
1
|
-
require 'api_client_builder/response'
|
2
|
-
|
3
1
|
module TestClient
|
4
2
|
class ResponseHandler
|
5
3
|
SUCCESS_STATUS = 200
|
6
|
-
SUCCESS_RANGE = [200]
|
4
|
+
SUCCESS_RANGE = [200].freeze
|
7
5
|
MAX_RETRIES = 4
|
8
6
|
|
9
|
-
def initialize(
|
7
|
+
def initialize(_client, _start_url, _type, **_opts)
|
10
8
|
@retries = 0
|
11
9
|
end
|
12
10
|
|
13
11
|
def get_first_page
|
14
12
|
@current_page = 0
|
15
|
-
APIClientBuilder::Response.new([1,2,3], SUCCESS_STATUS, SUCCESS_RANGE)
|
13
|
+
APIClientBuilder::Response.new([1, 2, 3], SUCCESS_STATUS, SUCCESS_RANGE)
|
16
14
|
end
|
17
15
|
|
18
16
|
def get_next_page
|
19
17
|
@current_page += 1
|
20
|
-
APIClientBuilder::Response.new([4,5,6], SUCCESS_STATUS, SUCCESS_RANGE)
|
18
|
+
APIClientBuilder::Response.new([4, 5, 6], SUCCESS_STATUS, SUCCESS_RANGE)
|
21
19
|
end
|
22
20
|
|
23
|
-
def put_request(
|
21
|
+
def put_request(_body)
|
24
22
|
APIClientBuilder::Response.new('good request', SUCCESS_STATUS, SUCCESS_RANGE)
|
25
23
|
end
|
26
24
|
|
27
|
-
def post_request(
|
25
|
+
def post_request(_body)
|
28
26
|
APIClientBuilder::Response.new('good request', SUCCESS_STATUS, SUCCESS_RANGE)
|
29
27
|
end
|
30
28
|
|
31
29
|
def more_pages?
|
32
|
-
|
33
|
-
return true
|
30
|
+
@current_page < 2
|
34
31
|
end
|
35
32
|
|
36
33
|
def retryable?(status_code)
|
@@ -38,10 +35,8 @@ module TestClient
|
|
38
35
|
status_code != 400 && @retries < MAX_RETRIES
|
39
36
|
end
|
40
37
|
|
41
|
-
def retry_request
|
42
|
-
end
|
38
|
+
def retry_request; end
|
43
39
|
|
44
|
-
def reset_retries
|
45
|
-
end
|
40
|
+
def reset_retries; end
|
46
41
|
end
|
47
42
|
end
|
@@ -1,12 +1,11 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'api_client_builder/url_generator'
|
3
2
|
|
4
3
|
module APIClientBuilder
|
5
4
|
describe URLGenerator do
|
6
5
|
describe '#build_route' do
|
7
6
|
context 'route with colon params and matching keys' do
|
8
7
|
it 'replaces to route keys with their respective values' do
|
9
|
-
url_generator = URLGenerator.new(
|
8
|
+
url_generator = URLGenerator.new('https://www.domain.com/api/endpoints/')
|
10
9
|
|
11
10
|
route = url_generator.build_route(
|
12
11
|
'object_one/:object_one_id/:object_one_id/route_to_object/:other_object_id/object',
|
@@ -20,12 +19,14 @@ module APIClientBuilder
|
|
20
19
|
|
21
20
|
context 'route with colon params and non matching keys' do
|
22
21
|
it 'raises an argument error' do
|
23
|
-
url_generator = URLGenerator.new(
|
22
|
+
url_generator = URLGenerator.new('https://www.domain.com/api/endpoints/')
|
24
23
|
|
25
|
-
expect
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
expect do
|
25
|
+
url_generator.build_route(
|
26
|
+
'object_one/:object_one_id/:object_one_id/route_to_object/:other_object_id/object',
|
27
|
+
other_object_id: '10'
|
28
|
+
)
|
29
|
+
end.to raise_error(ArgumentError, 'Param :object_one_id is required')
|
29
30
|
end
|
30
31
|
end
|
31
32
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter 'lib/api_client_builder/version.rb'
|
4
|
+
add_filter 'spec'
|
5
|
+
track_files 'lib/**/*.rb'
|
6
|
+
end
|
7
|
+
SimpleCov.minimum_coverage(97)
|
8
|
+
|
1
9
|
require 'bundler/setup'
|
2
10
|
require 'api_client_builder'
|
3
|
-
|
4
|
-
SPEC_DIR = File.dirname(__FILE__)
|
metadata
CHANGED
@@ -1,77 +1,90 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api_client_builder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jayce Higgins
|
8
|
+
- Bryan Petty
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2018-06-26 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: pry
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
16
17
|
requirements:
|
17
|
-
- -
|
18
|
+
- - ">="
|
18
19
|
- !ruby/object:Gem::Version
|
19
20
|
version: '0'
|
20
21
|
type: :development
|
21
22
|
prerelease: false
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
23
24
|
requirements:
|
24
|
-
- -
|
25
|
+
- - ">="
|
25
26
|
- !ruby/object:Gem::Version
|
26
27
|
version: '0'
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
29
|
name: rspec
|
29
30
|
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
|
-
- -
|
32
|
+
- - "~>"
|
32
33
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
+
version: '3.7'
|
34
35
|
type: :development
|
35
36
|
prerelease: false
|
36
37
|
version_requirements: !ruby/object:Gem::Requirement
|
37
38
|
requirements:
|
38
|
-
- -
|
39
|
+
- - "~>"
|
39
40
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
+
version: '3.7'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rubocop
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 0.57.2
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 0.57.2
|
41
56
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
57
|
+
name: simplecov
|
43
58
|
requirement: !ruby/object:Gem::Requirement
|
44
59
|
requirements:
|
45
|
-
- -
|
60
|
+
- - "~>"
|
46
61
|
- !ruby/object:Gem::Version
|
47
62
|
version: '0'
|
48
63
|
type: :development
|
49
64
|
prerelease: false
|
50
65
|
version_requirements: !ruby/object:Gem::Requirement
|
51
66
|
requirements:
|
52
|
-
- -
|
67
|
+
- - "~>"
|
53
68
|
- !ruby/object:Gem::Version
|
54
69
|
version: '0'
|
55
70
|
description: API Client Builder provides an easy to use interface for creating HTTP
|
56
71
|
api clients
|
57
72
|
email:
|
58
73
|
- jhiggins@instructure.com
|
74
|
+
- bpetty@instructure.com
|
59
75
|
- eng@instructure.com
|
60
76
|
executables: []
|
61
77
|
extensions: []
|
62
78
|
extra_rdoc_files: []
|
63
79
|
files:
|
64
|
-
- .
|
65
|
-
- .
|
66
|
-
- .travis.yml
|
67
|
-
- Dockerfile
|
80
|
+
- ".gitignore"
|
81
|
+
- ".rubocop.yml"
|
82
|
+
- ".travis.yml"
|
68
83
|
- Gemfile
|
69
84
|
- LICENSE.txt
|
70
85
|
- README.md
|
71
|
-
- api_client_builder-1.0.0.gem
|
72
86
|
- api_client_builder.gemspec
|
73
87
|
- build.sh
|
74
|
-
- docker-compose.yml
|
75
88
|
- lib/api_client_builder.rb
|
76
89
|
- lib/api_client_builder/api_client.rb
|
77
90
|
- lib/api_client_builder/get_collection_request.rb
|
@@ -94,7 +107,7 @@ files:
|
|
94
107
|
- spec/lib/api_client_builder/test_client/response_handler.rb
|
95
108
|
- spec/lib/api_client_builder/url_generator_spec.rb
|
96
109
|
- spec/spec_helper.rb
|
97
|
-
homepage:
|
110
|
+
homepage: https://github.com/instructure/api-client-builder
|
98
111
|
licenses:
|
99
112
|
- MIT
|
100
113
|
metadata: {}
|
@@ -104,17 +117,17 @@ require_paths:
|
|
104
117
|
- lib
|
105
118
|
required_ruby_version: !ruby/object:Gem::Requirement
|
106
119
|
requirements:
|
107
|
-
- -
|
120
|
+
- - ">="
|
108
121
|
- !ruby/object:Gem::Version
|
109
|
-
version: '2.
|
122
|
+
version: '2.3'
|
110
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
124
|
requirements:
|
112
|
-
- -
|
125
|
+
- - ">="
|
113
126
|
- !ruby/object:Gem::Version
|
114
127
|
version: '0'
|
115
128
|
requirements: []
|
116
129
|
rubyforge_project:
|
117
|
-
rubygems_version: 2.
|
130
|
+
rubygems_version: 2.5.1
|
118
131
|
signing_key:
|
119
132
|
specification_version: 4
|
120
133
|
summary: API Client Builder provides an easy to use interface for creating HTTP api
|
data/.dockerignore
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
Gemfile.lock
|
data/Dockerfile
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
FROM instructure/rvm
|
2
|
-
MAINTAINER Instructure
|
3
|
-
|
4
|
-
COPY Gemfile* *.gemspec /usr/src/app/
|
5
|
-
COPY lib/api_client_builder/version.rb /usr/src/app/lib/api_client_builder/
|
6
|
-
|
7
|
-
USER root
|
8
|
-
RUN chown -R docker:docker /usr/src/app
|
9
|
-
USER docker
|
10
|
-
RUN /bin/bash -l -c "cd /usr/src/app && bundle install"
|
11
|
-
|
12
|
-
COPY . /usr/src/app
|
13
|
-
USER root
|
14
|
-
RUN chown -R docker:docker /usr/src/app/*
|
15
|
-
USER docker
|
16
|
-
CMD /bin/bash -l -c "cd /usr/src/app && pwd && bundle exec wwtd --parallel"
|
Binary file
|
data/docker-compose.yml
DELETED