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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed94b089c0e0f86ccf4acec03af52e2b23e6b0c7f284b3a947e9c15280fc208e
4
- data.tar.gz: 7858cb787ce243ee49ad7d953d747f859116f0cc47906d13235faa71e629f1ca
3
+ metadata.gz: 94095125b2db1864337d568c4851db76ed2998ddb1460c4e244a4967c89b3572
4
+ data.tar.gz: b2bb5c5bdeeb2e2cf3d70c35af9707fbc3f1229d785fdfe91baabf74405fb596
5
5
  SHA512:
6
- metadata.gz: '096660ad1bde2cec7fbfbcf532879cc415a702c467e5dc687469f2b77aaa064a94a82a3a1efc5f480d35408adb0af8c870d686fe5ab11b5842a2c51eff58097e'
7
- data.tar.gz: 2e15e44494be6edd6fbb29f1469b70708567d071a4936b1b58902c96e7a0958e1a5de10e8de8abcbf66baeaaab99152c93752cec9045ce11dbba459072fbc938
6
+ metadata.gz: 84d8a950dc40d46406a8bf05c9941589170e06d643d8270a8d132caea2f10309e2bf3818407f36e8225fca114856040c73a28c86a0802668be8b118d76284795
7
+ data.tar.gz: ad8a7edb768481560ab97c0eda80570ca6c65239a10cdd89fce84958be1d3008c23835691f36c6242e7acf7716bf9523ec4948cb8e0bf71f90b4de983fbac32b
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.5'
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
@@ -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&.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
- 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,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
- "#\{#{$1}\}"
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
- return unless body
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
- body.merge!(options[:body]) if options[:body]
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) if query
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.5
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-07-18 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: