client-api-builder 0.2.4 → 0.2.8
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/Gemfile.lock +1 -1
- data/client-api-builder.gemspec +3 -1
- data/examples/basic_auth_example_client.rb +20 -0
- data/lib/client-api-builder.rb +3 -1
- data/lib/client_api_builder/nested_router.rb +100 -0
- data/lib/client_api_builder/router.rb +84 -11
- data/lib/client_api_builder/section.rb +34 -0
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9853c5806a999fe126ad1b1ce5fdeb6ea6bdb985142e36a1cbdcbbbac6434ae
|
4
|
+
data.tar.gz: 9bfb059e5adcbf18c55882d1fcca7d04d310e04dce915cd3d46e88f770e36650
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1896be3318d8f5362c8b367d9836854d8567f9e7a394cac645c126a866afd2eb53507721228ae78f7bc94d5625b5fdfe2abe2273320de04c844e94cbe1b8df5e
|
7
|
+
data.tar.gz: 90aaaf4e7a73452397c699f095b383397c9a9d715b91b20431f0faf798b00e716d387eef9280f8687e118538c6e9c1e56f894979559b96b3671f04cf75f5ea6e
|
data/Gemfile.lock
CHANGED
data/client-api-builder.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'client-api-builder'
|
5
|
-
s.version = '0.2.
|
5
|
+
s.version = '0.2.8'
|
6
6
|
s.licenses = ['MIT']
|
7
7
|
s.summary = 'Develop Client API libraries faster'
|
8
8
|
s.description = 'Utility for constructing API clients'
|
@@ -10,4 +10,6 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.email = 'dougyouch@gmail.com'
|
11
11
|
s.homepage = 'https://github.com/dougyouch/client-api-builder'
|
12
12
|
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
13
|
+
|
14
|
+
s.add_runtime_dependency 'inheritance-helper', '>= 0.2.5'
|
13
15
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'base64'
|
2
|
+
require 'securerandom'
|
2
3
|
|
3
4
|
class BasicAuthExampleClient < Struct.new(
|
4
5
|
:username,
|
@@ -6,6 +7,7 @@ class BasicAuthExampleClient < Struct.new(
|
|
6
7
|
)
|
7
8
|
|
8
9
|
include ClientApiBuilder::Router
|
10
|
+
include ClientApiBuilder::Section
|
9
11
|
|
10
12
|
base_url 'https://www.example.com'
|
11
13
|
|
@@ -15,9 +17,27 @@ class BasicAuthExampleClient < Struct.new(
|
|
15
17
|
route :get_apps, '/apps'
|
16
18
|
route :get_app, '/apps/:app_id'
|
17
19
|
|
20
|
+
section :users do
|
21
|
+
header 'Authorization', :bearer_authorization
|
22
|
+
|
23
|
+
route :create_user, '/users?z={cache_buster}'
|
24
|
+
end
|
25
|
+
|
26
|
+
def cache_buster
|
27
|
+
(Time.now.to_f * 1000).to_i
|
28
|
+
end
|
29
|
+
|
18
30
|
private
|
19
31
|
|
32
|
+
def auth_token
|
33
|
+
@auth_token ||= SecureRandom.uuid
|
34
|
+
end
|
35
|
+
|
20
36
|
def basic_authorization
|
21
37
|
'basic ' + Base64.strict_encode64(username + ':' + password)
|
22
38
|
end
|
39
|
+
|
40
|
+
def bearer_authorization
|
41
|
+
'bearer ' + auth_token
|
42
|
+
end
|
23
43
|
end
|
data/lib/client-api-builder.rb
CHANGED
@@ -11,8 +11,10 @@ module ClientApiBuilder
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
autoload :
|
14
|
+
autoload :NestedRouter, 'client_api_builder/nested_router'
|
15
15
|
autoload :QueryParams, 'client_api_builder/query_params'
|
16
|
+
autoload :Router, 'client_api_builder/router'
|
17
|
+
autoload :Section, 'client_api_builder/section'
|
16
18
|
|
17
19
|
module NetHTTP
|
18
20
|
autoload :Request, 'client_api_builder/net_http_request'
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Purpose: to nest routers, which are sub sections of APIs
|
4
|
+
# for example if you had an entire section of your API dedicatd to user management.
|
5
|
+
# you may want to nest all calls to those routes under the user section
|
6
|
+
# ex: client.users.get_user(id: 1) # where users is a nested router
|
7
|
+
module ClientApiBuilder
|
8
|
+
class NestedRouter
|
9
|
+
include ::ClientApiBuilder::Router
|
10
|
+
|
11
|
+
attr_reader :root_router
|
12
|
+
|
13
|
+
def initialize(root_router)
|
14
|
+
@root_router = root_router
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.get_instance_method(var)
|
18
|
+
"\#{root_router.#{var}\}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def request(**options, &block)
|
22
|
+
root_router.request(**options, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def stream(**options, &block)
|
26
|
+
root_router.stream(**options, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def stream_to_io(**options, &block)
|
30
|
+
root_router.stream_to_io(**options, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def stream_to_file(**options, &block)
|
34
|
+
root_router.stream_to_file(**options, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def base_url(options)
|
38
|
+
self.class.base_url || root_router.base_url(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_headers(options)
|
42
|
+
headers = root_router.build_headers(options)
|
43
|
+
|
44
|
+
add_header_proc = proc do |name, value|
|
45
|
+
headers[name] =
|
46
|
+
if value.is_a?(Proc)
|
47
|
+
root_router.instance_eval(&value)
|
48
|
+
elsif value.is_a?(Symbol)
|
49
|
+
root_router.send(value)
|
50
|
+
else
|
51
|
+
value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
self.class.headers.each(&add_header_proc)
|
56
|
+
|
57
|
+
headers
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_connection_options(options)
|
61
|
+
root_router.build_connection_options(options)
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_query(query, options)
|
65
|
+
return nil if query.nil? && root_router.class.query_params.empty? && self.class.query_params.empty?
|
66
|
+
|
67
|
+
query_params = {}
|
68
|
+
|
69
|
+
add_query_param_proc = proc do |name, value|
|
70
|
+
query_params[name] =
|
71
|
+
if value.is_a?(Proc)
|
72
|
+
root_router.instance_eval(&value)
|
73
|
+
elsif value.is_a?(Symbol)
|
74
|
+
root_router.send(value)
|
75
|
+
else
|
76
|
+
value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
root_router.class.query_params.each(&add_query_param_proc)
|
81
|
+
self.class.query_params.each(&add_query_param_proc)
|
82
|
+
query && query.each(&add_query_param_proc)
|
83
|
+
options[:query] && options[:query].each(&add_query_param_proc)
|
84
|
+
|
85
|
+
self.class.build_query(query_params)
|
86
|
+
end
|
87
|
+
|
88
|
+
def build_body(body, options)
|
89
|
+
root_router.build_body(body, options)
|
90
|
+
end
|
91
|
+
|
92
|
+
def expected_response_code!(response, expected_response_codes, options)
|
93
|
+
root_router.expected_response_code!(response, expected_response_codes, options)
|
94
|
+
end
|
95
|
+
|
96
|
+
def handle_response(response, options, &block)
|
97
|
+
root_router.handle_response(response, options, &block)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -6,8 +6,9 @@ module ClientApiBuilder
|
|
6
6
|
def self.included(base)
|
7
7
|
base.extend InheritanceHelper::Methods
|
8
8
|
base.extend ClassMethods
|
9
|
+
base.include ::ClientApiBuilder::Section
|
9
10
|
base.include ::ClientApiBuilder::NetHTTP::Request
|
10
|
-
base.attr_reader :response, :request_options
|
11
|
+
base.send(:attr_reader, :response, :request_options)
|
11
12
|
end
|
12
13
|
|
13
14
|
module ClassMethods
|
@@ -20,19 +21,39 @@ module ClientApiBuilder
|
|
20
21
|
def default_options
|
21
22
|
{
|
22
23
|
base_url: nil,
|
23
|
-
|
24
|
+
body_builder: :to_json,
|
24
25
|
connection_options: {},
|
26
|
+
headers: {},
|
27
|
+
query_builder: Hash.method_defined?(:to_query) ? :to_query : :query_params,
|
25
28
|
query_params: {},
|
26
|
-
|
29
|
+
response_procs: {}
|
27
30
|
}.freeze
|
28
31
|
end
|
29
32
|
|
33
|
+
|
34
|
+
def add_response_procs(method_name, proc)
|
35
|
+
response_procs = default_options[:response_procs].dup
|
36
|
+
response_procs[method_name] = proc
|
37
|
+
add_value_to_class_method(:default_options, response_procs: response_procs)
|
38
|
+
end
|
39
|
+
|
40
|
+
def response_proc(method_name)
|
41
|
+
proc = default_options[:response_procs][method_name]
|
42
|
+
proc
|
43
|
+
end
|
44
|
+
|
30
45
|
def base_url(url = nil)
|
31
46
|
return default_options[:base_url] unless url
|
32
47
|
|
33
48
|
add_value_to_class_method(:default_options, base_url: url)
|
34
49
|
end
|
35
50
|
|
51
|
+
def body_builder(builder = nil)
|
52
|
+
return default_options[:body_builder] unless builder
|
53
|
+
|
54
|
+
add_value_to_class_method(:default_options, body_builder: builder)
|
55
|
+
end
|
56
|
+
|
36
57
|
def query_builder(builder = nil)
|
37
58
|
return default_options[:query_builder] unless builder
|
38
59
|
|
@@ -69,6 +90,21 @@ module ClientApiBuilder
|
|
69
90
|
default_options[:query_params]
|
70
91
|
end
|
71
92
|
|
93
|
+
def build_body(router, body, options)
|
94
|
+
builder = options[:body_builder] || body_builder
|
95
|
+
|
96
|
+
case builder
|
97
|
+
when :to_json
|
98
|
+
body.to_json
|
99
|
+
when :to_query
|
100
|
+
body.to_query
|
101
|
+
when :query_params
|
102
|
+
ClientApiBuilder::QueryParams.to_query(body)
|
103
|
+
else
|
104
|
+
router.instance_exec(body, &builder)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
72
108
|
def build_query(query)
|
73
109
|
case query_builder
|
74
110
|
when :to_query
|
@@ -111,6 +147,8 @@ module ClientApiBuilder
|
|
111
147
|
arguments += get_hash_arguments(v)
|
112
148
|
when Array
|
113
149
|
arguments += get_array_arguments(v)
|
150
|
+
when String
|
151
|
+
hsh[k] = "__||#{$1}||__" if v =~ /\{([a-z0-9_]+)\}/i
|
114
152
|
end
|
115
153
|
end
|
116
154
|
arguments
|
@@ -127,6 +165,8 @@ module ClientApiBuilder
|
|
127
165
|
arguments += get_hash_arguments(v)
|
128
166
|
when Array
|
129
167
|
arguments += get_array_arguments(v)
|
168
|
+
when String
|
169
|
+
list[idx] = "__||#{$1}||__" if v =~ /\{([a-z0-9_]+)\}/i
|
130
170
|
end
|
131
171
|
end
|
132
172
|
arguments
|
@@ -143,9 +183,31 @@ module ClientApiBuilder
|
|
143
183
|
end
|
144
184
|
end
|
145
185
|
|
186
|
+
def get_instance_method(var)
|
187
|
+
"#\{#{var}\}"
|
188
|
+
end
|
189
|
+
|
190
|
+
@@namespaces = []
|
191
|
+
def namespaces
|
192
|
+
@@namespaces
|
193
|
+
end
|
194
|
+
|
195
|
+
def namespace(name)
|
196
|
+
namespaces << name
|
197
|
+
yield
|
198
|
+
namespaces.pop
|
199
|
+
end
|
200
|
+
|
146
201
|
def generate_route_code(method_name, path, options = {})
|
147
202
|
http_method = options[:method] || http_method(method_name)
|
148
203
|
|
204
|
+
path = namespaces.join + path
|
205
|
+
|
206
|
+
# instance method
|
207
|
+
path.gsub!(/\{([a-z0-9_]+)\}/i) do |_|
|
208
|
+
get_instance_method($1)
|
209
|
+
end
|
210
|
+
|
149
211
|
path_arguments = []
|
150
212
|
path.gsub!(/:([a-z0-9_]+)/i) do |_|
|
151
213
|
path_arguments << $1
|
@@ -207,6 +269,7 @@ module ClientApiBuilder
|
|
207
269
|
method_args += ['**__options__', '&block']
|
208
270
|
|
209
271
|
code = "def #{method_name}(" + method_args.join(', ') + ")\n"
|
272
|
+
code += " block ||= self.class.response_proc(#{method_name.inspect})\n"
|
210
273
|
code += " __path__ = \"#{path}\"\n"
|
211
274
|
code += " __query__ = #{query}\n"
|
212
275
|
code += " __body__ = #{body}\n"
|
@@ -244,13 +307,15 @@ module ClientApiBuilder
|
|
244
307
|
code
|
245
308
|
end
|
246
309
|
|
247
|
-
def route(method_name, path, options = {})
|
310
|
+
def route(method_name, path, options = {}, &block)
|
311
|
+
add_response_procs(method_name, block) if block
|
312
|
+
|
248
313
|
self.class_eval generate_route_code(method_name, path, options), __FILE__, __LINE__
|
249
314
|
end
|
250
315
|
end
|
251
316
|
|
252
317
|
def base_url(options)
|
253
|
-
self.class.base_url
|
318
|
+
options[:base_url] || self.class.base_url
|
254
319
|
end
|
255
320
|
|
256
321
|
def build_headers(options)
|
@@ -268,7 +333,7 @@ module ClientApiBuilder
|
|
268
333
|
end
|
269
334
|
|
270
335
|
self.class.headers.each(&add_header_proc)
|
271
|
-
options[:headers]
|
336
|
+
options[:headers] && options[:headers].each(&add_header_proc)
|
272
337
|
|
273
338
|
headers
|
274
339
|
end
|
@@ -282,6 +347,8 @@ module ClientApiBuilder
|
|
282
347
|
end
|
283
348
|
|
284
349
|
def build_query(query, options)
|
350
|
+
return nil if query.nil? && self.class.query_params.empty?
|
351
|
+
|
285
352
|
query_params = {}
|
286
353
|
|
287
354
|
add_query_param_proc = proc do |name, value|
|
@@ -296,22 +363,24 @@ module ClientApiBuilder
|
|
296
363
|
end
|
297
364
|
|
298
365
|
self.class.query_params.each(&add_query_param_proc)
|
299
|
-
query
|
366
|
+
query && query.each(&add_query_param_proc)
|
367
|
+
options[:query] && options[:query].each(&add_query_param_proc)
|
300
368
|
|
301
369
|
self.class.build_query(query_params)
|
302
370
|
end
|
303
371
|
|
304
372
|
def build_body(body, options)
|
305
|
-
|
373
|
+
body = options[:body] if options.key?(:body)
|
374
|
+
|
375
|
+
return nil unless body
|
306
376
|
return body if body.is_a?(String)
|
307
377
|
|
308
|
-
|
309
|
-
body.to_json
|
378
|
+
self.class.build_body(self, body, options)
|
310
379
|
end
|
311
380
|
|
312
381
|
def build_uri(path, query, options)
|
313
382
|
uri = URI(base_url(options) + path)
|
314
|
-
uri.query = build_query(query, options)
|
383
|
+
uri.query = build_query(query, options)
|
315
384
|
uri
|
316
385
|
end
|
317
386
|
|
@@ -343,5 +412,9 @@ module ClientApiBuilder
|
|
343
412
|
data
|
344
413
|
end
|
345
414
|
end
|
415
|
+
|
416
|
+
def root_router
|
417
|
+
self
|
418
|
+
end
|
346
419
|
end
|
347
420
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Purpose is to encapsulate adding nested routers
|
4
|
+
module ClientApiBuilder
|
5
|
+
module Section
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def section(name, &block)
|
12
|
+
kls = InheritanceHelper::ClassBuilder::Utils.create_class(
|
13
|
+
self,
|
14
|
+
name,
|
15
|
+
::ClientApiBuilder::NestedRouter,
|
16
|
+
nil,
|
17
|
+
'NestedRouter',
|
18
|
+
&block
|
19
|
+
)
|
20
|
+
|
21
|
+
code = <<CODE
|
22
|
+
def self.#{name}_router
|
23
|
+
#{kls.name}
|
24
|
+
end
|
25
|
+
|
26
|
+
def #{name}
|
27
|
+
@#{name} ||= self.class.#{name}_router.new(self.root_router)
|
28
|
+
end
|
29
|
+
CODE
|
30
|
+
self.class_eval code, __FILE__, __LINE__
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: client-api-builder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Doug Youch
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
12
|
-
dependencies:
|
11
|
+
date: 2021-08-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: inheritance-helper
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.2.5
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.5
|
13
27
|
description: Utility for constructing API clients
|
14
28
|
email: dougyouch@gmail.com
|
15
29
|
executables: []
|
@@ -27,9 +41,11 @@ files:
|
|
27
41
|
- examples/basic_auth_example_client.rb
|
28
42
|
- examples/imdb_datasets_client.rb
|
29
43
|
- lib/client-api-builder.rb
|
44
|
+
- lib/client_api_builder/nested_router.rb
|
30
45
|
- lib/client_api_builder/net_http_request.rb
|
31
46
|
- lib/client_api_builder/query_params.rb
|
32
47
|
- lib/client_api_builder/router.rb
|
48
|
+
- lib/client_api_builder/section.rb
|
33
49
|
- script/console
|
34
50
|
homepage: https://github.com/dougyouch/client-api-builder
|
35
51
|
licenses:
|