api_client_builder 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/instructure/api-client-builder.svg?branch=master)](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