client-api-builder 0.2.4 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe20c0bab4626c805bc2b0c982cdd2bf27ed6b83d601a2b4be3377da74d34d65
4
- data.tar.gz: c82184aadfac611f12f57060bc315d9790b4bab7238ba4c2942334058b5fdafd
3
+ metadata.gz: b9853c5806a999fe126ad1b1ce5fdeb6ea6bdb985142e36a1cbdcbbbac6434ae
4
+ data.tar.gz: 9bfb059e5adcbf18c55882d1fcca7d04d310e04dce915cd3d46e88f770e36650
5
5
  SHA512:
6
- metadata.gz: b3df586e352a828dc8423862f425ce0524c4e479c10f4e35b6a514b92412277dc4d0c095afbdaaeb024b5783c38745f3d1fe2bebb31b34f097611b0dca77d7ad
7
- data.tar.gz: 42c359200e625705ce7960ae88d861d49cf5a83ad7e3dca71ee240f0df36addb88a7488e9da656a6d545b27cafe6075f09b8aab2242363896aa0321fb3399bf5
6
+ metadata.gz: 1896be3318d8f5362c8b367d9836854d8567f9e7a394cac645c126a866afd2eb53507721228ae78f7bc94d5625b5fdfe2abe2273320de04c844e94cbe1b8df5e
7
+ data.tar.gz: 90aaaf4e7a73452397c699f095b383397c9a9d715b91b20431f0faf798b00e716d387eef9280f8687e118538c6e9c1e56f894979559b96b3671f04cf75f5ea6e
data/Gemfile.lock CHANGED
@@ -9,7 +9,7 @@ GEM
9
9
  diff-lcs (1.4.4)
10
10
  docile (1.4.0)
11
11
  hashdiff (1.0.1)
12
- inheritance-helper (0.1.5)
12
+ inheritance-helper (0.2.5)
13
13
  parallel (1.20.1)
14
14
  parser (3.0.1.1)
15
15
  ast (~> 2.4.1)
@@ -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.4'
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
@@ -11,8 +11,10 @@ module ClientApiBuilder
11
11
  end
12
12
  end
13
13
 
14
- autoload :Router, 'client_api_builder/router'
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
- headers: {},
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
- query_builder: Hash.method_defined?(:to_query) ? :to_query : :query_params
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]&.each(&add_header_proc)
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&.each(&add_query_param_proc)
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
- return unless body
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
- body.merge!(options[:body]) if options[:body]
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) if query
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
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-07-17 00:00:00.000000000 Z
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: