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 +4 -4
- data/.ruby-version +1 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +40 -28
- data/client-api-builder.gemspec +1 -1
- data/examples/basic_auth_example_client.rb +2 -0
- data/lib/client-api-builder.rb +6 -0
- data/lib/client_api_builder/active_support_log_subscriber.rb +29 -0
- data/lib/client_api_builder/active_support_notifications.rb +25 -0
- data/lib/client_api_builder/nested_router.rb +17 -10
- data/lib/client_api_builder/query_params.rb +31 -18
- data/lib/client_api_builder/router.rb +138 -47
- data/lib/client_api_builder/section.rb +2 -2
- data/script/console +4 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06500a9c514d0979b01f672ae5bdac70c5dfc7b23b3633f0662a71c4a057a01b
|
4
|
+
data.tar.gz: c3a00f73d888ad6a19b98a9a0c172685fd5299b61fa99b41931c427db8d73470
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2263f684643292a86895f23906e2d566043d914704b92db919a1f23e1d47c1f5371ef7ea344257a63b234aac6ce8e5f293d4cf16b20e08248c7859fb8b692448
|
7
|
+
data.tar.gz: 0a2508597031c861ea8ba44ac00f9fcd3596b040bde9df1d3724afdb4c5a0d4b4b463d19e75b0360a2987ba1ad7375bff8c25572cb6fced4e194a560bb6f927e
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.1.0
|
data/Gemfile
CHANGED
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.
|
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
|
-
|
14
|
-
|
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.
|
18
|
-
rake (13.0.
|
19
|
-
regexp_parser (2.
|
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.
|
22
|
-
rspec-core (~> 3.
|
23
|
-
rspec-expectations (~> 3.
|
24
|
-
rspec-mocks (~> 3.
|
25
|
-
rspec-core (3.
|
26
|
-
rspec-support (~> 3.
|
27
|
-
rspec-expectations (3.
|
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.
|
30
|
-
rspec-mocks (3.
|
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.
|
33
|
-
rspec-support (3.
|
34
|
-
rubocop (1.
|
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.
|
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.
|
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.
|
44
|
-
parser (>= 3.
|
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.
|
52
|
-
|
53
|
-
|
54
|
-
|
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-
|
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.
|
82
|
+
2.3.3
|
data/client-api-builder.gemspec
CHANGED
data/lib/client-api-builder.rb
CHANGED
@@ -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
|
38
|
-
self.class.base_url || root_router.base_url
|
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.
|
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.
|
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.
|
81
|
-
self.class.
|
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
|
-
|
6
|
-
|
5
|
+
class QueryParams
|
6
|
+
attr_reader :name_value_separator,
|
7
|
+
:param_separator
|
7
8
|
|
8
|
-
|
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 ?
|
20
|
+
to_query_from_hash(data, (namespace ? escape(namespace) : nil)).join(param_separator)
|
12
21
|
when Array
|
13
|
-
to_query_from_array(data, (namespace ? "#{
|
22
|
+
to_query_from_array(data, (namespace ? "#{escape(namespace)}[]" : '[]')).join(param_separator)
|
14
23
|
else
|
15
24
|
if namespace
|
16
|
-
"#{
|
25
|
+
"#{escape(namespace)}#{name_value_separator}#{escape(data.to_s)}"
|
17
26
|
else
|
18
|
-
|
27
|
+
escape(data.to_s)
|
19
28
|
end
|
20
29
|
end
|
21
30
|
end
|
22
31
|
|
23
|
-
def to_query_from_hash(hsh, namespace
|
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}[#{
|
30
|
-
query_params += to_query_from_array(value, array_namespace
|
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}[#{
|
33
|
-
query_params += to_query_from_hash(value, hash_namespace
|
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}[#{
|
36
|
-
query_params << "#{query_name}#{name_value_separator}#{
|
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
|
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
|
58
|
+
query_params += to_query_from_hash(value, namespace)
|
50
59
|
when Array
|
51
|
-
query_params += to_query_from_array(value, "#{namespace}[]"
|
60
|
+
query_params += to_query_from_array(value, "#{namespace}[]")
|
52
61
|
else
|
53
|
-
query_params << "#{namespace}#{name_value_separator}#{
|
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.
|
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
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
52
|
-
|
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
|
-
|
58
|
-
|
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
|
-
|
100
|
+
# get default headers
|
101
|
+
def default_headers
|
82
102
|
default_options[:headers]
|
83
103
|
end
|
84
104
|
|
85
|
-
|
105
|
+
# get configured connection_options
|
106
|
+
def default_connection_options
|
86
107
|
default_options[:connection_options]
|
87
108
|
end
|
88
109
|
|
89
|
-
|
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
|
94
|
-
|
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, &
|
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
|
-
|
139
|
+
router.instance_exec(query, &query_builder)
|
116
140
|
end
|
117
141
|
end
|
118
142
|
|
119
|
-
def
|
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] ||
|
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 += "
|
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 += "
|
330
|
+
code += " @response\n"
|
300
331
|
elsif options[:return] == :body
|
301
|
-
code += "
|
332
|
+
code += " @response.body\n"
|
302
333
|
else
|
303
|
-
code += "
|
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
|
-
|
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
|
318
|
-
|
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.
|
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.
|
375
|
+
self.class.default_connection_options.merge(options[:connection_options])
|
344
376
|
else
|
345
|
-
self.class.
|
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.
|
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.
|
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
|
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
|
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.
|
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:
|
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.
|
71
|
+
rubygems_version: 3.3.3
|
70
72
|
signing_key:
|
71
73
|
specification_version: 4
|
72
74
|
summary: Develop Client API libraries faster
|