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