client-api-builder 0.2.5 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|