client-api-builder 0.2.8 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9853c5806a999fe126ad1b1ce5fdeb6ea6bdb985142e36a1cbdcbbbac6434ae
4
- data.tar.gz: 9bfb059e5adcbf18c55882d1fcca7d04d310e04dce915cd3d46e88f770e36650
3
+ metadata.gz: 06500a9c514d0979b01f672ae5bdac70c5dfc7b23b3633f0662a71c4a057a01b
4
+ data.tar.gz: c3a00f73d888ad6a19b98a9a0c172685fd5299b61fa99b41931c427db8d73470
5
5
  SHA512:
6
- metadata.gz: 1896be3318d8f5362c8b367d9836854d8567f9e7a394cac645c126a866afd2eb53507721228ae78f7bc94d5625b5fdfe2abe2273320de04c844e94cbe1b8df5e
7
- data.tar.gz: 90aaaf4e7a73452397c699f095b383397c9a9d715b91b20431f0faf798b00e716d387eef9280f8687e118538c6e9c1e56f894979559b96b3671f04cf75f5ea6e
6
+ metadata.gz: 2263f684643292a86895f23906e2d566043d914704b92db919a1f23e1d47c1f5371ef7ea344257a63b234aac6ce8e5f293d4cf16b20e08248c7859fb8b692448
7
+ data.tar.gz: 0a2508597031c861ea8ba44ac00f9fcd3596b040bde9df1d3724afdb4c5a0d4b4b463d19e75b0360a2987ba1ad7375bff8c25572cb6fced4e194a560bb6f927e
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.0.0
1
+ 3.1.0
data/Gemfile CHANGED
@@ -10,6 +10,7 @@ group :development do
10
10
  end
11
11
 
12
12
  group :spec do
13
+ gem 'activesupport'
13
14
  gem 'rspec'
14
15
  gem 'simplecov'
15
16
  gem 'webmock'
data/Gemfile.lock CHANGED
@@ -1,64 +1,76 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
+ activesupport (7.0.2.3)
5
+ concurrent-ruby (~> 1.0, >= 1.0.2)
6
+ i18n (>= 1.6, < 2)
7
+ minitest (>= 5.1)
8
+ tzinfo (~> 2.0)
4
9
  addressable (2.8.0)
5
10
  public_suffix (>= 2.0.2, < 5.0)
6
11
  ast (2.4.2)
12
+ concurrent-ruby (1.1.9)
7
13
  crack (0.4.5)
8
14
  rexml
9
- diff-lcs (1.4.4)
15
+ diff-lcs (1.5.0)
10
16
  docile (1.4.0)
11
17
  hashdiff (1.0.1)
18
+ i18n (1.10.0)
19
+ concurrent-ruby (~> 1.0)
12
20
  inheritance-helper (0.2.5)
13
- parallel (1.20.1)
14
- parser (3.0.1.1)
21
+ minitest (5.15.0)
22
+ parallel (1.21.0)
23
+ parser (3.1.1.0)
15
24
  ast (~> 2.4.1)
16
25
  public_suffix (4.0.6)
17
- rainbow (3.0.0)
18
- rake (13.0.3)
19
- regexp_parser (2.1.1)
26
+ rainbow (3.1.1)
27
+ rake (13.0.6)
28
+ regexp_parser (2.2.1)
20
29
  rexml (3.2.5)
21
- rspec (3.10.0)
22
- rspec-core (~> 3.10.0)
23
- rspec-expectations (~> 3.10.0)
24
- rspec-mocks (~> 3.10.0)
25
- rspec-core (3.10.1)
26
- rspec-support (~> 3.10.0)
27
- rspec-expectations (3.10.1)
30
+ rspec (3.11.0)
31
+ rspec-core (~> 3.11.0)
32
+ rspec-expectations (~> 3.11.0)
33
+ rspec-mocks (~> 3.11.0)
34
+ rspec-core (3.11.0)
35
+ rspec-support (~> 3.11.0)
36
+ rspec-expectations (3.11.0)
28
37
  diff-lcs (>= 1.2.0, < 2.0)
29
- rspec-support (~> 3.10.0)
30
- rspec-mocks (3.10.2)
38
+ rspec-support (~> 3.11.0)
39
+ rspec-mocks (3.11.0)
31
40
  diff-lcs (>= 1.2.0, < 2.0)
32
- rspec-support (~> 3.10.0)
33
- rspec-support (3.10.2)
34
- rubocop (1.18.2)
41
+ rspec-support (~> 3.11.0)
42
+ rspec-support (3.11.0)
43
+ rubocop (1.26.0)
35
44
  parallel (~> 1.10)
36
- parser (>= 3.0.0.0)
45
+ parser (>= 3.1.0.0)
37
46
  rainbow (>= 2.2.2, < 4.0)
38
47
  regexp_parser (>= 1.8, < 3.0)
39
48
  rexml
40
- rubocop-ast (>= 1.7.0, < 2.0)
49
+ rubocop-ast (>= 1.16.0, < 2.0)
41
50
  ruby-progressbar (~> 1.7)
42
51
  unicode-display_width (>= 1.4.0, < 3.0)
43
- rubocop-ast (1.7.0)
44
- parser (>= 3.0.1.1)
52
+ rubocop-ast (1.16.0)
53
+ parser (>= 3.1.1.0)
45
54
  ruby-progressbar (1.11.0)
46
55
  simplecov (0.21.2)
47
56
  docile (~> 1.1)
48
57
  simplecov-html (~> 0.11)
49
58
  simplecov_json_formatter (~> 0.1)
50
59
  simplecov-html (0.12.3)
51
- simplecov_json_formatter (0.1.3)
52
- unicode-display_width (2.0.0)
53
- webmock (3.13.0)
54
- addressable (>= 2.3.6)
60
+ simplecov_json_formatter (0.1.4)
61
+ tzinfo (2.0.4)
62
+ concurrent-ruby (~> 1.0)
63
+ unicode-display_width (2.1.0)
64
+ webmock (3.14.0)
65
+ addressable (>= 2.8.0)
55
66
  crack (>= 0.3.2)
56
67
  hashdiff (>= 0.4.0, < 2.0.0)
57
68
 
58
69
  PLATFORMS
59
- x86_64-darwin-20
70
+ x86_64-darwin-21
60
71
 
61
72
  DEPENDENCIES
73
+ activesupport
62
74
  inheritance-helper
63
75
  rake
64
76
  rspec
@@ -67,4 +79,4 @@ DEPENDENCIES
67
79
  webmock
68
80
 
69
81
  BUNDLED WITH
70
- 2.2.3
82
+ 2.3.3
@@ -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.8'
5
+ s.version = '0.3.1'
6
6
  s.licenses = ['MIT']
7
7
  s.summary = 'Develop Client API libraries faster'
8
8
  s.description = 'Utility for constructing API clients'
@@ -11,6 +11,8 @@ class BasicAuthExampleClient < Struct.new(
11
11
 
12
12
  base_url 'https://www.example.com'
13
13
 
14
+ configure_retries(2)
15
+
14
16
  header 'Authorization', :basic_authorization
15
17
  query_param('cache_buster') { (Time.now.to_f * 1000).to_i }
16
18
 
@@ -11,6 +11,12 @@ module ClientApiBuilder
11
11
  end
12
12
  end
13
13
 
14
+ class << self
15
+ attr_accessor :logger
16
+ end
17
+
18
+ autoload :ActiveSupportNotifications, 'client_api_builder/active_support_notifications'
19
+ autoload :ActiveSupportLogSubscriber, 'client_api_builder/active_support_log_subscriber'
14
20
  autoload :NestedRouter, 'client_api_builder/nested_router'
15
21
  autoload :QueryParams, 'client_api_builder/query_params'
16
22
  autoload :Router, 'client_api_builder/router'
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ require 'active_support'
3
+
4
+ # Purpose is to log all requests
5
+ module ClientApiBuilder
6
+ class ActiveSupportLogSubscriber
7
+ attr_reader :logger
8
+
9
+ def initialize(logger)
10
+ @logger = logger
11
+ end
12
+
13
+ def subscribe!
14
+ ActiveSupport::Notifications.subscribe('client_api_builder.request') do |event|
15
+ logger.info(generate_log_message(event))
16
+ end
17
+ end
18
+
19
+ def generate_log_message(event)
20
+ client = event.payload[:client]
21
+ method = client.request_options[:method].to_s.upcase
22
+ uri = client.request_options[:uri]
23
+ response = client.response
24
+ response_code = response ? response.code : 'UNKNOWN'
25
+
26
+ "#{method} #{uri.scheme}://#{uri.host}#{uri.path}[#{response_code}] took #{event.duration.to_i}ms"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ require 'active_support'
3
+
4
+ # Purpose is to change the instrument_request to use ActiveSupport::Notifications.instrument
5
+ module ClientApiBuilder
6
+ module ActiveSupportNotifications
7
+ def instrument_request
8
+ start_time = Time.now
9
+ error = nil
10
+ result = nil
11
+ ActiveSupport::Notifications.instrument('client_api_builder.request', client: self) do
12
+ begin
13
+ result = yield
14
+ rescue Exception => e
15
+ error = e
16
+ end
17
+ end
18
+
19
+ raise(error) if error
20
+ result
21
+ ensure
22
+ @total_request_time = Time.now - start_time
23
+ end
24
+ end
25
+ end
@@ -8,10 +8,12 @@ module ClientApiBuilder
8
8
  class NestedRouter
9
9
  include ::ClientApiBuilder::Router
10
10
 
11
- attr_reader :root_router
11
+ attr_reader :root_router,
12
+ :nested_router_options
12
13
 
13
- def initialize(root_router)
14
+ def initialize(root_router, nested_router_options)
14
15
  @root_router = root_router
16
+ @nested_router_options = nested_router_options
15
17
  end
16
18
 
17
19
  def self.get_instance_method(var)
@@ -34,12 +36,12 @@ module ClientApiBuilder
34
36
  root_router.stream_to_file(**options, &block)
35
37
  end
36
38
 
37
- def base_url(options)
38
- self.class.base_url || root_router.base_url(options)
39
+ def base_url
40
+ self.class.base_url || root_router.base_url
39
41
  end
40
42
 
41
43
  def build_headers(options)
42
- headers = root_router.build_headers(options)
44
+ headers = nested_router_options[:ignore_headers] ? {} : root_router.build_headers(options)
43
45
 
44
46
  add_header_proc = proc do |name, value|
45
47
  headers[name] =
@@ -52,7 +54,7 @@ module ClientApiBuilder
52
54
  end
53
55
  end
54
56
 
55
- self.class.headers.each(&add_header_proc)
57
+ self.class.default_headers.each(&add_header_proc)
56
58
 
57
59
  headers
58
60
  end
@@ -62,7 +64,8 @@ module ClientApiBuilder
62
64
  end
63
65
 
64
66
  def build_query(query, options)
65
- return nil if query.nil? && root_router.class.query_params.empty? && self.class.query_params.empty?
67
+ return nil if query.nil? && root_router.class.default_query_params.empty? && self.class.default_query_params.empty?
68
+ return nil if nested_router_options[:ignore_query] && query.nil? && self.class.default_query_params.empty?
66
69
 
67
70
  query_params = {}
68
71
 
@@ -77,12 +80,12 @@ module ClientApiBuilder
77
80
  end
78
81
  end
79
82
 
80
- root_router.class.query_params.each(&add_query_param_proc)
81
- self.class.query_params.each(&add_query_param_proc)
83
+ root_router.class.default_query_params.each(&add_query_param_proc)
84
+ self.class.default_query_params.each(&add_query_param_proc)
82
85
  query && query.each(&add_query_param_proc)
83
86
  options[:query] && options[:query].each(&add_query_param_proc)
84
87
 
85
- self.class.build_query(query_params)
88
+ self.class.build_query(self, query_params)
86
89
  end
87
90
 
88
91
  def build_body(body, options)
@@ -96,5 +99,9 @@ module ClientApiBuilder
96
99
  def handle_response(response, options, &block)
97
100
  root_router.handle_response(response, options, &block)
98
101
  end
102
+
103
+ def escape_path(path)
104
+ root_router.escape_path(path)
105
+ end
99
106
  end
100
107
  end
@@ -2,59 +2,72 @@
2
2
  require 'cgi'
3
3
 
4
4
  module ClientApiBuilder
5
- module QueryParams
6
- module_function
5
+ class QueryParams
6
+ attr_reader :name_value_separator,
7
+ :param_separator
7
8
 
8
- def to_query(data, namespace = nil, name_value_separator = '=', param_separator = '&')
9
+ attr_accessor :custom_escape_proc
10
+
11
+ def initialize(name_value_separator: '=', param_separator: '&', custom_escape_proc: nil)
12
+ @name_value_separator = name_value_separator
13
+ @param_separator = param_separator
14
+ @custom_escape_proc = custom_escape_proc
15
+ end
16
+
17
+ def to_query(data, namespace = nil)
9
18
  case data
10
19
  when Hash
11
- to_query_from_hash(data, (namespace ? CGI.escape(namespace) : nil), name_value_separator).join(param_separator)
20
+ to_query_from_hash(data, (namespace ? escape(namespace) : nil)).join(param_separator)
12
21
  when Array
13
- to_query_from_array(data, (namespace ? "#{CGI.escape(namespace)}[]" : '[]'), name_value_separator).join(param_separator)
22
+ to_query_from_array(data, (namespace ? "#{escape(namespace)}[]" : '[]')).join(param_separator)
14
23
  else
15
24
  if namespace
16
- "#{CGI.escape(namespace)}#{name_value_separator}#{CGI.escape(data.to_s)}"
25
+ "#{escape(namespace)}#{name_value_separator}#{escape(data.to_s)}"
17
26
  else
18
- CGI.escape(data.to_s)
27
+ escape(data.to_s)
19
28
  end
20
29
  end
21
30
  end
22
31
 
23
- def to_query_from_hash(hsh, namespace, name_value_separator)
32
+ def to_query_from_hash(hsh, namespace)
24
33
  query_params = []
25
34
 
26
35
  hsh.each do |key, value|
27
36
  case value
28
37
  when Array
29
- array_namespace = namespace ? "#{namespace}[#{CGI.escape(key.to_s)}][]" : "#{CGI.escape(key.to_s)}[]"
30
- query_params += to_query_from_array(value, array_namespace, name_value_separator)
38
+ array_namespace = namespace ? "#{namespace}[#{escape(key.to_s)}][]" : "#{escape(key.to_s)}[]"
39
+ query_params += to_query_from_array(value, array_namespace)
31
40
  when Hash
32
- hash_namespace = namespace ? "#{namespace}[#{CGI.escape(key.to_s)}]" : "#{CGI.escape(key.to_s)}"
33
- query_params += to_query_from_hash(value, hash_namespace, name_value_separator)
41
+ hash_namespace = namespace ? "#{namespace}[#{escape(key.to_s)}]" : "#{escape(key.to_s)}"
42
+ query_params += to_query_from_hash(value, hash_namespace)
34
43
  else
35
- query_name = namespace ? "#{namespace}[#{CGI.escape(key.to_s)}]" : "#{CGI.escape(key.to_s)}"
36
- query_params << "#{query_name}#{name_value_separator}#{CGI.escape(value.to_s)}"
44
+ query_name = namespace ? "#{namespace}[#{escape(key.to_s)}]" : "#{escape(key.to_s)}"
45
+ query_params << "#{query_name}#{name_value_separator}#{escape(value.to_s)}"
37
46
  end
38
47
  end
39
48
 
40
49
  query_params
41
50
  end
42
51
 
43
- def to_query_from_array(array, namespace, name_value_separator)
52
+ def to_query_from_array(array, namespace)
44
53
  query_params = []
45
54
 
46
55
  array.each do |value|
47
56
  case value
48
57
  when Hash
49
- query_params += to_query_from_hash(value, namespace, name_value_separator)
58
+ query_params += to_query_from_hash(value, namespace)
50
59
  when Array
51
- query_params += to_query_from_array(value, "#{namespace}[]", name_value_separator)
60
+ query_params += to_query_from_array(value, "#{namespace}[]")
52
61
  else
53
- query_params << "#{namespace}#{name_value_separator}#{CGI.escape(value.to_s)}"
62
+ query_params << "#{namespace}#{name_value_separator}#{escape(value.to_s)}"
54
63
  end
55
64
  end
56
65
 
57
66
  query_params
58
67
  end
68
+
69
+ def escape(str)
70
+ custom_escape_proc ? custom_escape_proc.call(str) : CGI.escape(str)
71
+ end
59
72
  end
60
73
  end
@@ -8,7 +8,8 @@ module ClientApiBuilder
8
8
  base.extend ClassMethods
9
9
  base.include ::ClientApiBuilder::Section
10
10
  base.include ::ClientApiBuilder::NetHTTP::Request
11
- base.send(:attr_reader, :response, :request_options)
11
+ base.include(::ClientApiBuilder::ActiveSupportNotifications) if defined?(ActiveSupport)
12
+ base.send(:attr_reader, :response, :request_options, :total_request_time, :retry_attempts)
12
13
  end
13
14
 
14
15
  module ClassMethods
@@ -26,97 +27,120 @@ module ClientApiBuilder
26
27
  headers: {},
27
28
  query_builder: Hash.method_defined?(:to_query) ? :to_query : :query_params,
28
29
  query_params: {},
29
- response_procs: {}
30
+ response_procs: {},
31
+ max_retries: 1,
32
+ sleep: 0.05
30
33
  }.freeze
31
34
  end
32
35
 
33
-
34
- def add_response_procs(method_name, proc)
36
+ # tracks the proc used to handle responses
37
+ def add_response_proc(method_name, proc)
35
38
  response_procs = default_options[:response_procs].dup
36
39
  response_procs[method_name] = proc
37
40
  add_value_to_class_method(:default_options, response_procs: response_procs)
38
41
  end
39
42
 
40
- def response_proc(method_name)
41
- proc = default_options[:response_procs][method_name]
42
- proc
43
+ # retrieves the proc used to handle the response
44
+ def get_response_proc(method_name)
45
+ default_options[:response_procs][method_name]
43
46
  end
44
47
 
48
+ # set/get base url
45
49
  def base_url(url = nil)
46
50
  return default_options[:base_url] unless url
47
51
 
48
52
  add_value_to_class_method(:default_options, base_url: url)
49
53
  end
50
54
 
51
- def body_builder(builder = nil)
52
- return default_options[:body_builder] unless builder
55
+ # set the builder to :to_json, :to_query, :query_params or specify a proc to handle building the request body payload
56
+ # or get the body builder
57
+ def body_builder(builder = nil, &block)
58
+ return default_options[:body_builder] if builder.nil? && block.nil?
53
59
 
54
- add_value_to_class_method(:default_options, body_builder: builder)
60
+ add_value_to_class_method(:default_options, body_builder: builder || block)
55
61
  end
56
62
 
57
- def query_builder(builder = nil)
58
- return default_options[:query_builder] unless builder
63
+ # set the builder to :to_query, :query_params or specify a proc to handle building the request query params
64
+ # or get the query builder
65
+ def query_builder(builder = nil, &block)
66
+ return default_options[:query_builder] if builder.nil? && block.nil?
59
67
 
60
- add_value_to_class_method(:default_options, query_builder: builder)
68
+ add_value_to_class_method(:default_options, query_builder: builder || block)
61
69
  end
62
70
 
71
+ # add a request header
63
72
  def header(name, value = nil, &block)
64
73
  headers = default_options[:headers].dup
65
74
  headers[name] = value || block
66
75
  add_value_to_class_method(:default_options, headers: headers)
67
76
  end
68
77
 
78
+ # set a connection_option, specific to Net::HTTP
69
79
  def connection_option(name, value)
70
80
  connection_options = default_options[:connection_options].dup
71
81
  connection_options[name] = value
72
82
  add_value_to_class_method(:default_options, connection_options: connection_options)
73
83
  end
74
84
 
85
+ def configure_retries(max_retries, sleep_time_between_retries_in_seconds = 0.05)
86
+ add_value_to_class_method(
87
+ :default_options,
88
+ max_retries: max_retries,
89
+ sleep: sleep_time_between_retries_in_seconds
90
+ )
91
+ end
92
+
93
+ # add a query param to all requests
75
94
  def query_param(name, value = nil, &block)
76
95
  query_params = default_options[:query_params].dup
77
96
  query_params[name] = value || block
78
97
  add_value_to_class_method(:default_options, query_params: query_params)
79
98
  end
80
99
 
81
- def headers
100
+ # get default headers
101
+ def default_headers
82
102
  default_options[:headers]
83
103
  end
84
104
 
85
- def connection_options
105
+ # get configured connection_options
106
+ def default_connection_options
86
107
  default_options[:connection_options]
87
108
  end
88
109
 
89
- def query_params
110
+ # get default query_params to add to all requests
111
+ def default_query_params
90
112
  default_options[:query_params]
91
113
  end
92
114
 
93
- def build_body(router, body, options)
94
- builder = options[:body_builder] || body_builder
95
-
96
- case builder
115
+ def build_body(router, body)
116
+ case body_builder
97
117
  when :to_json
98
118
  body.to_json
99
119
  when :to_query
100
120
  body.to_query
101
121
  when :query_params
102
- ClientApiBuilder::QueryParams.to_query(body)
122
+ ClientApiBuilder::QueryParams.new.to_query(body)
123
+ when Symbol
124
+ router.send(body_builder, body)
103
125
  else
104
- router.instance_exec(body, &builder)
126
+ router.instance_exec(body, &body_builder)
105
127
  end
106
128
  end
107
129
 
108
- def build_query(query)
130
+ def build_query(router, query)
109
131
  case query_builder
110
132
  when :to_query
111
133
  query.to_query
112
134
  when :query_params
113
- ClientApiBuilder::QueryParams.to_query(query)
135
+ ClientApiBuilder::QueryParams.new.to_query(query)
136
+ when Symbol
137
+ router.send(query_builder, query)
114
138
  else
115
- query_builder.call(query)
139
+ router.instance_exec(query, &query_builder)
116
140
  end
117
141
  end
118
142
 
119
- def http_method(method_name)
143
+ def auto_detect_http_method(method_name)
120
144
  case method_name.to_s
121
145
  when /^(?:post|create|add|insert)/i
122
146
  :post
@@ -172,6 +196,7 @@ module ClientApiBuilder
172
196
  arguments
173
197
  end
174
198
 
199
+ # returns a list of arguments to add to the route method
175
200
  def get_arguments(value)
176
201
  case value
177
202
  when Hash
@@ -184,7 +209,7 @@ module ClientApiBuilder
184
209
  end
185
210
 
186
211
  def get_instance_method(var)
187
- "#\{#{var}\}"
212
+ "#\{escape_path(#{var})\}"
188
213
  end
189
214
 
190
215
  @@namespaces = []
@@ -192,6 +217,7 @@ module ClientApiBuilder
192
217
  @@namespaces
193
218
  end
194
219
 
220
+ # a namespace is a top level path to apply to all routes within the namespace block
195
221
  def namespace(name)
196
222
  namespaces << name
197
223
  yield
@@ -199,7 +225,7 @@ module ClientApiBuilder
199
225
  end
200
226
 
201
227
  def generate_route_code(method_name, path, options = {})
202
- http_method = options[:method] || http_method(method_name)
228
+ http_method = options[:method] || auto_detect_http_method(method_name)
203
229
 
204
230
  path = namespaces.join + path
205
231
 
@@ -211,7 +237,7 @@ module ClientApiBuilder
211
237
  path_arguments = []
212
238
  path.gsub!(/:([a-z0-9_]+)/i) do |_|
213
239
  path_arguments << $1
214
- "#\{#{$1}\}"
240
+ "#\{escape_path(#{$1})\}"
215
241
  end
216
242
 
217
243
  has_body_param = options[:body].nil? && requires_body?(http_method, options)
@@ -268,12 +294,10 @@ module ClientApiBuilder
268
294
  method_args += ["#{stream_param}:"] if stream_param
269
295
  method_args += ['**__options__', '&block']
270
296
 
271
- code = "def #{method_name}(" + method_args.join(', ') + ")\n"
272
- code += " block ||= self.class.response_proc(#{method_name.inspect})\n"
297
+ code = "def #{method_name}_raw_response(" + method_args.join(', ') + ")\n"
273
298
  code += " __path__ = \"#{path}\"\n"
274
299
  code += " __query__ = #{query}\n"
275
300
  code += " __body__ = #{body}\n"
276
- code += " __expected_response_codes__ = #{expected_response_codes.inspect}\n"
277
301
  code += " __uri__ = build_uri(__path__, __query__, __options__)\n"
278
302
  code += " __body__ = build_body(__body__, __options__)\n"
279
303
  code += " __headers__ = build_headers(__options__)\n"
@@ -292,30 +316,38 @@ module ClientApiBuilder
292
316
  else
293
317
  code += " @response = request(**@request_options)\n"
294
318
  end
319
+ code += "end\n"
320
+ code += "\n"
295
321
 
296
- code += " expected_response_code!(@response, __expected_response_codes__, __options__)\n"
322
+ code += "def #{method_name}(" + method_args.join(', ') + ")\n"
323
+ code += " request_wrapper(__options__) do\n"
324
+ code += " block ||= self.class.get_response_proc(#{method_name.inspect})\n"
325
+ code += " __expected_response_codes__ = #{expected_response_codes.inspect}\n"
326
+ code += " #{method_name}_raw_response(" + method_args.map { |a| a =~ /:$/ ? "#{a} #{a.sub(':', '')}" : a }.join(', ') + ")\n"
327
+ code += " expected_response_code!(@response, __expected_response_codes__, __options__)\n"
297
328
 
298
329
  if options[:stream] || options[:return] == :response
299
- code += " @response\n"
330
+ code += " @response\n"
300
331
  elsif options[:return] == :body
301
- code += " @response.body\n"
332
+ code += " @response.body\n"
302
333
  else
303
- code += " handle_response(@response, __options__, &block)\n"
334
+ code += " handle_response(@response, __options__, &block)\n"
304
335
  end
305
336
 
337
+ code += " end\n"
306
338
  code += "end\n"
307
339
  code
308
340
  end
309
341
 
310
342
  def route(method_name, path, options = {}, &block)
311
- add_response_procs(method_name, block) if block
343
+ add_response_proc(method_name, block) if block
312
344
 
313
345
  self.class_eval generate_route_code(method_name, path, options), __FILE__, __LINE__
314
346
  end
315
347
  end
316
348
 
317
- def base_url(options)
318
- options[:base_url] || self.class.base_url
349
+ def base_url
350
+ self.class.base_url
319
351
  end
320
352
 
321
353
  def build_headers(options)
@@ -332,7 +364,7 @@ module ClientApiBuilder
332
364
  end
333
365
  end
334
366
 
335
- self.class.headers.each(&add_header_proc)
367
+ self.class.default_headers.each(&add_header_proc)
336
368
  options[:headers] && options[:headers].each(&add_header_proc)
337
369
 
338
370
  headers
@@ -340,14 +372,14 @@ module ClientApiBuilder
340
372
 
341
373
  def build_connection_options(options)
342
374
  if options[:connection_options]
343
- self.class.connection_options.merge(options[:connection_options])
375
+ self.class.default_connection_options.merge(options[:connection_options])
344
376
  else
345
- self.class.connection_options
377
+ self.class.default_connection_options
346
378
  end
347
379
  end
348
380
 
349
381
  def build_query(query, options)
350
- return nil if query.nil? && self.class.query_params.empty?
382
+ return nil if query.nil? && self.class.default_query_params.empty?
351
383
 
352
384
  query_params = {}
353
385
 
@@ -362,11 +394,11 @@ module ClientApiBuilder
362
394
  end
363
395
  end
364
396
 
365
- self.class.query_params.each(&add_query_param_proc)
397
+ self.class.default_query_params.each(&add_query_param_proc)
366
398
  query && query.each(&add_query_param_proc)
367
399
  options[:query] && options[:query].each(&add_query_param_proc)
368
400
 
369
- self.class.build_query(query_params)
401
+ self.class.build_query(self, query_params)
370
402
  end
371
403
 
372
404
  def build_body(body, options)
@@ -375,11 +407,11 @@ module ClientApiBuilder
375
407
  return nil unless body
376
408
  return body if body.is_a?(String)
377
409
 
378
- self.class.build_body(self, body, options)
410
+ self.class.build_body(self, body)
379
411
  end
380
412
 
381
413
  def build_uri(path, query, options)
382
- uri = URI(base_url(options) + path)
414
+ uri = URI(base_url + path)
383
415
  uri.query = build_query(query, options)
384
416
  uri
385
417
  end
@@ -416,5 +448,64 @@ module ClientApiBuilder
416
448
  def root_router
417
449
  self
418
450
  end
451
+
452
+ def escape_path(path)
453
+ path
454
+ end
455
+
456
+ def instrument_request
457
+ start_time = Time.now
458
+ yield
459
+ ensure
460
+ @total_request_time = Time.now - start_time
461
+ end
462
+
463
+ def retry_request(options)
464
+ @request_attempts = 0
465
+ max_attempts = get_retry_request_max_retries(options)
466
+ begin
467
+ @request_attempts += 1
468
+ yield
469
+ rescue Exception => e
470
+ log_request_exception(e)
471
+ raise(e) if @request_attempts >= max_attempts || !retry_request?(e, options)
472
+ sleep_time = get_retry_request_sleep_time(e, options)
473
+ sleep(sleep_time) if sleep_time && sleep_time > 0
474
+ retry
475
+ end
476
+ end
477
+
478
+ def get_retry_request_sleep_time(e, options)
479
+ options[:sleep] || self.class.default_options[:sleep] || 0.05
480
+ end
481
+
482
+ def get_retry_request_max_retries(options)
483
+ options[:retries] || self.class.default_options[:max_retries] || 1
484
+ end
485
+
486
+ def request_wrapper(options)
487
+ retry_request(options) do
488
+ instrument_request do
489
+ yield
490
+ end
491
+ end
492
+ end
493
+
494
+ def retry_request?(exception, options)
495
+ true
496
+ end
497
+
498
+ def log_request_exception(exception)
499
+ ::ClientApiBuilder.logger && ::ClientApiBuilder.logger.error(exception)
500
+ end
501
+
502
+ def request_log_message
503
+ method = request_options[:method].to_s.upcase
504
+ uri = request_options[:uri]
505
+ response_code = response ? response.code : 'UNKNOWN'
506
+
507
+ duration = (total_request_time * 1000).to_i
508
+ "#{method} #{uri.scheme}://#{uri.host}#{uri.path}[#{response_code}] took #{duration}ms"
509
+ end
419
510
  end
420
511
  end
@@ -8,7 +8,7 @@ module ClientApiBuilder
8
8
  end
9
9
 
10
10
  module ClassMethods
11
- def section(name, &block)
11
+ def section(name, nested_router_options={}, &block)
12
12
  kls = InheritanceHelper::ClassBuilder::Utils.create_class(
13
13
  self,
14
14
  name,
@@ -24,7 +24,7 @@ def self.#{name}_router
24
24
  end
25
25
 
26
26
  def #{name}
27
- @#{name} ||= self.class.#{name}_router.new(self.root_router)
27
+ @#{name} ||= self.class.#{name}_router.new(self.root_router, #{nested_router_options.inspect})
28
28
  end
29
29
  CODE
30
30
  self.class_eval code, __FILE__, __LINE__
data/script/console CHANGED
@@ -6,5 +6,9 @@ $LOAD_PATH << File.expand_path('../examples', __dir__)
6
6
  require 'client-api-builder'
7
7
  autoload :BasicAuthExampleClient, 'basic_auth_example_client'
8
8
  autoload :IMDBDatesetsClient, 'imdb_datasets_client'
9
+ require 'logger'
10
+ LOG = Logger.new(STDOUT)
11
+ ClientApiBuilder.logger = LOG
12
+ ClientApiBuilder::ActiveSupportLogSubscriber.new(LOG).subscribe!
9
13
  require 'irb'
10
14
  IRB.start(__FILE__)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: client-api-builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.3.1
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-08-16 00:00:00.000000000 Z
11
+ date: 2022-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inheritance-helper
@@ -41,6 +41,8 @@ files:
41
41
  - examples/basic_auth_example_client.rb
42
42
  - examples/imdb_datasets_client.rb
43
43
  - lib/client-api-builder.rb
44
+ - lib/client_api_builder/active_support_log_subscriber.rb
45
+ - lib/client_api_builder/active_support_notifications.rb
44
46
  - lib/client_api_builder/nested_router.rb
45
47
  - lib/client_api_builder/net_http_request.rb
46
48
  - lib/client_api_builder/query_params.rb
@@ -66,7 +68,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
68
  - !ruby/object:Gem::Version
67
69
  version: '0'
68
70
  requirements: []
69
- rubygems_version: 3.2.3
71
+ rubygems_version: 3.3.3
70
72
  signing_key:
71
73
  specification_version: 4
72
74
  summary: Develop Client API libraries faster