client-api-builder 0.2.5 → 0.2.6
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 +77 -9
- 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: 94095125b2db1864337d568c4851db76ed2998ddb1460c4e244a4967c89b3572
|
|
4
|
+
data.tar.gz: b2bb5c5bdeeb2e2cf3d70c35af9707fbc3f1229d785fdfe91baabf74405fb596
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 84d8a950dc40d46406a8bf05c9941589170e06d643d8270a8d132caea2f10309e2bf3818407f36e8225fca114856040c73a28c86a0802668be8b118d76284795
|
|
7
|
+
data.tar.gz: ad8a7edb768481560ab97c0eda80570ca6c65239a10cdd89fce84958be1d3008c23835691f36c6242e7acf7716bf9523ec4948cb8e0bf71f90b4de983fbac32b
|
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.6'
|
|
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&.each(&add_query_param_proc)
|
|
83
|
+
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,6 +6,7 @@ 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
11
|
base.attr_reader :response, :request_options
|
|
11
12
|
end
|
|
@@ -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,12 +183,29 @@ 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
|
+
|
|
149
206
|
# instance method
|
|
150
207
|
path.gsub!(/\{([a-z0-9_]+)\}/i) do |_|
|
|
151
|
-
|
|
208
|
+
get_instance_method($1)
|
|
152
209
|
end
|
|
153
210
|
|
|
154
211
|
path_arguments = []
|
|
@@ -212,6 +269,7 @@ module ClientApiBuilder
|
|
|
212
269
|
method_args += ['**__options__', '&block']
|
|
213
270
|
|
|
214
271
|
code = "def #{method_name}(" + method_args.join(', ') + ")\n"
|
|
272
|
+
code += " block ||= self.class.response_proc(#{method_name.inspect})\n"
|
|
215
273
|
code += " __path__ = \"#{path}\"\n"
|
|
216
274
|
code += " __query__ = #{query}\n"
|
|
217
275
|
code += " __body__ = #{body}\n"
|
|
@@ -249,13 +307,15 @@ module ClientApiBuilder
|
|
|
249
307
|
code
|
|
250
308
|
end
|
|
251
309
|
|
|
252
|
-
def route(method_name, path, options = {})
|
|
310
|
+
def route(method_name, path, options = {}, &block)
|
|
311
|
+
add_response_procs(method_name, block) if block
|
|
312
|
+
|
|
253
313
|
self.class_eval generate_route_code(method_name, path, options), __FILE__, __LINE__
|
|
254
314
|
end
|
|
255
315
|
end
|
|
256
316
|
|
|
257
317
|
def base_url(options)
|
|
258
|
-
self.class.base_url
|
|
318
|
+
options[:base_url] || self.class.base_url
|
|
259
319
|
end
|
|
260
320
|
|
|
261
321
|
def build_headers(options)
|
|
@@ -287,6 +347,8 @@ module ClientApiBuilder
|
|
|
287
347
|
end
|
|
288
348
|
|
|
289
349
|
def build_query(query, options)
|
|
350
|
+
return nil if query.nil? && self.class.query_params.empty?
|
|
351
|
+
|
|
290
352
|
query_params = {}
|
|
291
353
|
|
|
292
354
|
add_query_param_proc = proc do |name, value|
|
|
@@ -302,21 +364,23 @@ module ClientApiBuilder
|
|
|
302
364
|
|
|
303
365
|
self.class.query_params.each(&add_query_param_proc)
|
|
304
366
|
query&.each(&add_query_param_proc)
|
|
367
|
+
options[:query]&.each(&add_query_param_proc)
|
|
305
368
|
|
|
306
369
|
self.class.build_query(query_params)
|
|
307
370
|
end
|
|
308
371
|
|
|
309
372
|
def build_body(body, options)
|
|
310
|
-
|
|
373
|
+
body = options[:body] if options.key?(:body)
|
|
374
|
+
|
|
375
|
+
return nil unless body
|
|
311
376
|
return body if body.is_a?(String)
|
|
312
377
|
|
|
313
|
-
|
|
314
|
-
body.to_json
|
|
378
|
+
self.class.build_body(self, body, options)
|
|
315
379
|
end
|
|
316
380
|
|
|
317
381
|
def build_uri(path, query, options)
|
|
318
382
|
uri = URI(base_url(options) + path)
|
|
319
|
-
uri.query = build_query(query, options)
|
|
383
|
+
uri.query = build_query(query, options)
|
|
320
384
|
uri
|
|
321
385
|
end
|
|
322
386
|
|
|
@@ -348,5 +412,9 @@ module ClientApiBuilder
|
|
|
348
412
|
data
|
|
349
413
|
end
|
|
350
414
|
end
|
|
415
|
+
|
|
416
|
+
def root_router
|
|
417
|
+
self
|
|
418
|
+
end
|
|
351
419
|
end
|
|
352
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.6
|
|
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:
|