lhc 13.0.0 → 15.0.0
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/.github/workflows/rubocop.yml +15 -0
- data/.github/workflows/test.yml +15 -0
- data/.rubocop.yml +344 -19
- data/.ruby-version +1 -1
- data/README.md +89 -0
- data/Rakefile +3 -3
- data/lhc.gemspec +3 -1
- data/lib/lhc.rb +70 -59
- data/lib/lhc/concerns/lhc/fix_invalid_encoding_concern.rb +1 -0
- data/lib/lhc/config.rb +16 -0
- data/lib/lhc/endpoint.rb +3 -0
- data/lib/lhc/error.rb +7 -4
- data/lib/lhc/interceptor.rb +4 -0
- data/lib/lhc/interceptors.rb +1 -0
- data/lib/lhc/interceptors/auth.rb +10 -5
- data/lib/lhc/interceptors/caching.rb +14 -3
- data/lib/lhc/interceptors/logging.rb +4 -2
- data/lib/lhc/interceptors/monitoring.rb +46 -11
- data/lib/lhc/interceptors/retry.rb +2 -0
- data/lib/lhc/interceptors/rollbar.rb +3 -2
- data/lib/lhc/interceptors/throttle.rb +7 -2
- data/lib/lhc/interceptors/zipkin.rb +2 -0
- data/lib/lhc/request.rb +37 -4
- data/lib/lhc/response.rb +1 -0
- data/lib/lhc/response/data.rb +1 -1
- data/lib/lhc/scrubber.rb +45 -0
- data/lib/lhc/scrubbers/auth_scrubber.rb +33 -0
- data/lib/lhc/scrubbers/body_scrubber.rb +28 -0
- data/lib/lhc/scrubbers/headers_scrubber.rb +38 -0
- data/lib/lhc/scrubbers/params_scrubber.rb +14 -0
- data/lib/lhc/version.rb +1 -1
- data/spec/config/scrubs_spec.rb +108 -0
- data/spec/error/to_s_spec.rb +13 -8
- data/spec/formats/multipart_spec.rb +2 -2
- data/spec/formats/plain_spec.rb +1 -1
- data/spec/interceptors/after_response_spec.rb +1 -1
- data/spec/interceptors/caching/main_spec.rb +2 -2
- data/spec/interceptors/caching/multilevel_cache_spec.rb +2 -1
- data/spec/interceptors/define_spec.rb +1 -0
- data/spec/interceptors/logging/main_spec.rb +21 -1
- data/spec/interceptors/monitoring/caching_spec.rb +66 -0
- data/spec/interceptors/response_competition_spec.rb +2 -2
- data/spec/interceptors/return_response_spec.rb +2 -2
- data/spec/interceptors/rollbar/main_spec.rb +27 -15
- data/spec/request/scrubbed_headers_spec.rb +101 -0
- data/spec/request/scrubbed_options_spec.rb +194 -0
- data/spec/request/scrubbed_params_spec.rb +35 -0
- data/spec/response/data_spec.rb +2 -2
- data/spec/support/zipkin_mock.rb +1 -0
- metadata +40 -21
- data/.rubocop.localch.yml +0 -325
- data/cider-ci.yml +0 -5
- data/cider-ci/bin/bundle +0 -51
- data/cider-ci/bin/ruby_install +0 -8
- data/cider-ci/bin/ruby_version +0 -25
- data/cider-ci/jobs/rspec-activesupport-5.yml +0 -27
- data/cider-ci/jobs/rspec-activesupport-6.yml +0 -28
- data/cider-ci/jobs/rubocop.yml +0 -18
- data/cider-ci/task_components/bundle.yml +0 -22
- data/cider-ci/task_components/rspec.yml +0 -36
- data/cider-ci/task_components/rubocop.yml +0 -29
- data/cider-ci/task_components/ruby.yml +0 -15
data/lib/lhc/error.rb
CHANGED
@@ -43,6 +43,7 @@ class LHC::Error < StandardError
|
|
43
43
|
|
44
44
|
def self.find(response)
|
45
45
|
return LHC::Timeout if response.timeout?
|
46
|
+
|
46
47
|
status_code = response.code.to_s[0..2].to_i
|
47
48
|
error = map[status_code]
|
48
49
|
error ||= LHC::UnknownError
|
@@ -64,17 +65,19 @@ class LHC::Error < StandardError
|
|
64
65
|
end
|
65
66
|
|
66
67
|
def to_s
|
67
|
-
return response
|
68
|
+
return response.to_s unless response.is_a?(LHC::Response)
|
69
|
+
|
68
70
|
request = response.request
|
71
|
+
return unless request.is_a?(LHC::Request)
|
72
|
+
|
69
73
|
debug = []
|
70
74
|
debug << [request.method, request.url].map { |str| self.class.fix_invalid_encoding(str) }.join(' ')
|
71
|
-
debug << "Options: #{request.
|
72
|
-
debug << "Headers: #{request.
|
75
|
+
debug << "Options: #{request.scrubbed_options}"
|
76
|
+
debug << "Headers: #{request.scrubbed_headers}"
|
73
77
|
debug << "Response Code: #{response.code} (#{response.options[:return_code]})"
|
74
78
|
debug << "Response Options: #{response.options}"
|
75
79
|
debug << response.body
|
76
80
|
debug << _message
|
77
|
-
|
78
81
|
debug.map { |str| self.class.fix_invalid_encoding(str) }.join("\n")
|
79
82
|
end
|
80
83
|
end
|
data/lib/lhc/interceptor.rb
CHANGED
data/lib/lhc/interceptors.rb
CHANGED
@@ -16,6 +16,7 @@ class LHC::Auth < LHC::Interceptor
|
|
16
16
|
def after_response
|
17
17
|
return unless configuration_correct?
|
18
18
|
return unless reauthenticate?
|
19
|
+
|
19
20
|
reauthenticate!
|
20
21
|
end
|
21
22
|
|
@@ -29,7 +30,7 @@ class LHC::Auth < LHC::Interceptor
|
|
29
30
|
def basic_authentication!
|
30
31
|
auth = auth_options[:basic]
|
31
32
|
credentials = "#{auth[:username]}:#{auth[:password]}"
|
32
|
-
|
33
|
+
set_basic_authorization_header(Base64.strict_encode64(credentials).chomp)
|
33
34
|
end
|
34
35
|
|
35
36
|
def bearer_authentication!
|
@@ -43,7 +44,13 @@ class LHC::Auth < LHC::Interceptor
|
|
43
44
|
request.headers['Authorization'] = value
|
44
45
|
end
|
45
46
|
|
47
|
+
def set_basic_authorization_header(base_64_encoded_credentials)
|
48
|
+
request.options[:auth][:basic].merge!(base_64_encoded_credentials: base_64_encoded_credentials)
|
49
|
+
set_authorization_header("Basic #{base_64_encoded_credentials}")
|
50
|
+
end
|
51
|
+
|
46
52
|
def set_bearer_authorization_header(token)
|
53
|
+
request.options[:auth].merge!(bearer_token: token)
|
47
54
|
set_authorization_header("Bearer #{token}")
|
48
55
|
end
|
49
56
|
# rubocop:enable Style/AccessorMethodName
|
@@ -75,10 +82,6 @@ class LHC::Auth < LHC::Interceptor
|
|
75
82
|
@refresh_client_token_option ||= auth_options[:refresh_client_token] || refresh_client_token
|
76
83
|
end
|
77
84
|
|
78
|
-
def all_interceptor_classes
|
79
|
-
@all_interceptors ||= LHC::Interceptors.new(request).all.map(&:class)
|
80
|
-
end
|
81
|
-
|
82
85
|
def auth_options
|
83
86
|
request.options[:auth] || {}
|
84
87
|
end
|
@@ -90,11 +93,13 @@ class LHC::Auth < LHC::Interceptor
|
|
90
93
|
|
91
94
|
def refresh_client_token?
|
92
95
|
return true if refresh_client_token_option.is_a?(Proc)
|
96
|
+
|
93
97
|
warn("[WARNING] The given refresh_client_token must be a Proc for reauthentication.")
|
94
98
|
end
|
95
99
|
|
96
100
|
def retry_interceptor?
|
97
101
|
return true if all_interceptor_classes.include?(LHC::Retry) && all_interceptor_classes.index(LHC::Retry) > all_interceptor_classes.index(self.class)
|
102
|
+
|
98
103
|
warn("[WARNING] Your interceptors must include LHC::Retry after LHC::Auth.")
|
99
104
|
end
|
100
105
|
end
|
@@ -41,15 +41,16 @@ class LHC::Caching < LHC::Interceptor
|
|
41
41
|
|
42
42
|
def before_request
|
43
43
|
return unless cache?(request)
|
44
|
-
|
45
|
-
|
46
|
-
return unless response_data
|
44
|
+
return if response_data.blank?
|
45
|
+
|
47
46
|
from_cache(request, response_data)
|
48
47
|
end
|
49
48
|
|
50
49
|
def after_response
|
51
50
|
return unless response.success?
|
52
51
|
return unless cache?(request)
|
52
|
+
return if response_data.present?
|
53
|
+
|
53
54
|
multilevel_cache.write(
|
54
55
|
key(request, options[:key]),
|
55
56
|
to_cache(response),
|
@@ -59,6 +60,14 @@ class LHC::Caching < LHC::Interceptor
|
|
59
60
|
|
60
61
|
private
|
61
62
|
|
63
|
+
# from cache
|
64
|
+
def response_data
|
65
|
+
# stop calling multi-level cache if it already returned nil for this interceptor instance
|
66
|
+
return @response_data if defined? @response_data
|
67
|
+
|
68
|
+
@response_data ||= multilevel_cache.fetch(key(request, options[:key]))
|
69
|
+
end
|
70
|
+
|
62
71
|
# performs read/write (fetch/write) on all configured cache levels (e.g. local & central)
|
63
72
|
def multilevel_cache
|
64
73
|
MultilevelCache.new(
|
@@ -75,6 +84,7 @@ class LHC::Caching < LHC::Interceptor
|
|
75
84
|
|
76
85
|
def central_cache
|
77
86
|
return nil if central.blank? || (central[:read].blank? && central[:write].blank?)
|
87
|
+
|
78
88
|
{}.tap do |options|
|
79
89
|
options[:read] = ActiveSupport::Cache::RedisCacheStore.new(url: central[:read]) if central[:read].present?
|
80
90
|
options[:write] = ActiveSupport::Cache::RedisCacheStore.new(url: central[:write]) if central[:write].present?
|
@@ -86,6 +96,7 @@ class LHC::Caching < LHC::Interceptor
|
|
86
96
|
# return false if this interceptor cannot work
|
87
97
|
def cache?(request)
|
88
98
|
return false unless request.options[:cache]
|
99
|
+
|
89
100
|
(local_cache || central_cache) &&
|
90
101
|
cached_method?(request.method, options[:methods])
|
91
102
|
end
|
@@ -7,14 +7,15 @@ class LHC::Logging < LHC::Interceptor
|
|
7
7
|
|
8
8
|
def before_request
|
9
9
|
return unless logger
|
10
|
+
|
10
11
|
logger.info(
|
11
12
|
[
|
12
13
|
'Before LHC request',
|
13
14
|
"<#{request.object_id}>",
|
14
15
|
request.method.upcase,
|
15
16
|
"#{request.url} at #{Time.now.iso8601}",
|
16
|
-
"Params=#{request.
|
17
|
-
"Headers=#{request.
|
17
|
+
"Params=#{request.scrubbed_params}",
|
18
|
+
"Headers=#{request.scrubbed_headers}",
|
18
19
|
request.source ? "\nCalled from #{request.source}" : nil
|
19
20
|
].compact.join(' ')
|
20
21
|
)
|
@@ -22,6 +23,7 @@ class LHC::Logging < LHC::Interceptor
|
|
22
23
|
|
23
24
|
def after_response
|
24
25
|
return unless logger
|
26
|
+
|
25
27
|
logger.info(
|
26
28
|
[
|
27
29
|
'After LHC response for request',
|
@@ -13,34 +13,64 @@ class LHC::Monitoring < LHC::Interceptor
|
|
13
13
|
|
14
14
|
def before_request
|
15
15
|
return unless statsd
|
16
|
-
|
16
|
+
|
17
|
+
LHC::Monitoring.statsd.count("#{key}.before_request", 1)
|
17
18
|
end
|
18
19
|
|
19
20
|
def after_request
|
20
21
|
return unless statsd
|
21
|
-
|
22
|
-
LHC::Monitoring.statsd.count("#{key
|
22
|
+
|
23
|
+
LHC::Monitoring.statsd.count("#{key}.count", 1)
|
24
|
+
LHC::Monitoring.statsd.count("#{key}.after_request", 1)
|
23
25
|
end
|
24
26
|
|
25
27
|
def after_response
|
26
28
|
return unless statsd
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
|
30
|
+
monitor_time!
|
31
|
+
monitor_cache!
|
32
|
+
monitor_response!
|
31
33
|
end
|
32
34
|
|
33
35
|
private
|
34
36
|
|
35
|
-
def
|
36
|
-
|
37
|
+
def monitor_time!
|
38
|
+
LHC::Monitoring.statsd.timing("#{key}.time", response.time) if response.success?
|
39
|
+
end
|
40
|
+
|
41
|
+
def monitor_cache!
|
42
|
+
return if request.options[:cache].blank?
|
43
|
+
return unless monitor_caching_configuration_check
|
44
|
+
|
45
|
+
if response.from_cache?
|
46
|
+
LHC::Monitoring.statsd.count("#{key}.cache.hit", 1)
|
47
|
+
else
|
48
|
+
LHC::Monitoring.statsd.count("#{key}.cache.miss", 1)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def monitor_caching_configuration_check
|
53
|
+
return true if all_interceptor_classes.include?(LHC::Caching) && all_interceptor_classes.index(self.class) > all_interceptor_classes.index(LHC::Caching)
|
54
|
+
|
55
|
+
warn("[WARNING] Your interceptors must include LHC::Caching and LHC::Monitoring and also in that order.")
|
56
|
+
end
|
57
|
+
|
58
|
+
def monitor_response!
|
59
|
+
if response.timeout?
|
60
|
+
LHC::Monitoring.statsd.count("#{key}.timeout", 1)
|
61
|
+
else
|
62
|
+
LHC::Monitoring.statsd.count("#{key}.#{response.code}", 1)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def key
|
37
67
|
key = options(request.options)[:key]
|
38
68
|
return key if key.present?
|
39
69
|
|
40
70
|
url = sanitize_url(request.url)
|
41
71
|
key = [
|
42
72
|
'lhc',
|
43
|
-
|
73
|
+
module_parent_name.underscore,
|
44
74
|
LHC::Monitoring.env || Rails.env,
|
45
75
|
URI.parse(url).host.gsub(/\./, '_'),
|
46
76
|
request.method
|
@@ -48,8 +78,13 @@ class LHC::Monitoring < LHC::Interceptor
|
|
48
78
|
key.join('.')
|
49
79
|
end
|
50
80
|
|
81
|
+
def module_parent_name
|
82
|
+
(ActiveSupport.gem_version >= Gem::Version.new('6.0.0')) ? Rails.application.class.module_parent_name : Rails.application.class.parent_name
|
83
|
+
end
|
84
|
+
|
51
85
|
def sanitize_url(url)
|
52
|
-
return url if url.match(%r{https?://})
|
86
|
+
return url if url.match?(%r{https?://})
|
87
|
+
|
53
88
|
"http://#{url}"
|
54
89
|
end
|
55
90
|
|
@@ -10,6 +10,7 @@ class LHC::Retry < LHC::Interceptor
|
|
10
10
|
def after_response
|
11
11
|
response.request.options[:retries] ||= 0
|
12
12
|
return unless retry?(response.request)
|
13
|
+
|
13
14
|
response.request.options[:retries] += 1
|
14
15
|
current_retry = response.request.options[:retries]
|
15
16
|
begin
|
@@ -26,6 +27,7 @@ class LHC::Retry < LHC::Interceptor
|
|
26
27
|
return false if request.response.success?
|
27
28
|
return false if request.error_ignored?
|
28
29
|
return false if !request.options.dig(:retry) && !LHC::Retry.all
|
30
|
+
|
29
31
|
request.options[:retries] < max(request)
|
30
32
|
end
|
31
33
|
|
@@ -9,6 +9,7 @@ class LHC::Rollbar < LHC::Interceptor
|
|
9
9
|
def after_response
|
10
10
|
return unless Object.const_defined?('Rollbar')
|
11
11
|
return if response.success?
|
12
|
+
|
12
13
|
request = response.request
|
13
14
|
additional_params = request.options.fetch(:rollbar, {})
|
14
15
|
data = {
|
@@ -22,8 +23,8 @@ class LHC::Rollbar < LHC::Interceptor
|
|
22
23
|
request: {
|
23
24
|
url: request.url,
|
24
25
|
method: request.method,
|
25
|
-
headers: request.
|
26
|
-
params: request.
|
26
|
+
headers: request.scrubbed_headers,
|
27
|
+
params: request.scrubbed_params
|
27
28
|
}
|
28
29
|
}.merge additional_params
|
29
30
|
begin
|
@@ -13,14 +13,17 @@ class LHC::Throttle < LHC::Interceptor
|
|
13
13
|
def before_request
|
14
14
|
options = request.options.dig(:throttle)
|
15
15
|
return unless options
|
16
|
+
|
16
17
|
break_options = options.dig(:break)
|
17
18
|
return unless break_options
|
18
|
-
|
19
|
+
|
20
|
+
break_when_quota_reached! if break_options.match?('%')
|
19
21
|
end
|
20
22
|
|
21
23
|
def after_response
|
22
24
|
options = response.request.options.dig(:throttle)
|
23
25
|
return unless throttle?(options)
|
26
|
+
|
24
27
|
self.class.track ||= {}
|
25
28
|
self.class.track[options.dig(:provider)] = {
|
26
29
|
limit: limit(options: options[:limit], response: response),
|
@@ -40,6 +43,7 @@ class LHC::Throttle < LHC::Interceptor
|
|
40
43
|
track = (self.class.track || {}).dig(options[:provider])
|
41
44
|
return if track.blank? || track[:remaining].blank? || track[:limit].blank? || track[:expires].blank?
|
42
45
|
return if Time.zone.now > track[:expires]
|
46
|
+
|
43
47
|
# avoid floats by multiplying with 100
|
44
48
|
remaining = track[:remaining] * 100
|
45
49
|
limit = track[:limit]
|
@@ -80,7 +84,8 @@ class LHC::Throttle < LHC::Interceptor
|
|
80
84
|
def convert_expires(value)
|
81
85
|
return if value.blank?
|
82
86
|
return value.call(response) if value.is_a?(Proc)
|
83
|
-
return Time.parse(value) if value.match(/GMT/)
|
87
|
+
return Time.parse(value) if value.match?(/GMT/)
|
88
|
+
|
84
89
|
Time.zone.at(value.to_i).to_datetime
|
85
90
|
end
|
86
91
|
end
|
@@ -12,6 +12,7 @@ class LHC::Zipkin < LHC::Interceptor
|
|
12
12
|
|
13
13
|
def before_request
|
14
14
|
return if !dependencies? || !tracing?
|
15
|
+
|
15
16
|
ZipkinTracer::TraceContainer.with_trace_id(trace_id) do
|
16
17
|
# add headers even if the current trace_id should not be sampled
|
17
18
|
B3_HEADERS.each { |method, header| request.headers[header] = trace_id.send(method).to_s }
|
@@ -23,6 +24,7 @@ class LHC::Zipkin < LHC::Interceptor
|
|
23
24
|
def after_response
|
24
25
|
# only sample the current call if we're instructed to do so
|
25
26
|
return unless dependencies? && trace_id.sampled?
|
27
|
+
|
26
28
|
end_trace!
|
27
29
|
end
|
28
30
|
|
data/lib/lhc/request.rb
CHANGED
@@ -12,7 +12,13 @@ class LHC::Request
|
|
12
12
|
|
13
13
|
TYPHOEUS_OPTIONS ||= [:params, :method, :body, :headers, :follow_location, :params_encoding]
|
14
14
|
|
15
|
-
attr_accessor :response,
|
15
|
+
attr_accessor :response,
|
16
|
+
:options,
|
17
|
+
:raw,
|
18
|
+
:format,
|
19
|
+
:error_handler,
|
20
|
+
:errors_ignored,
|
21
|
+
:source
|
16
22
|
|
17
23
|
def initialize(options, self_executing = true)
|
18
24
|
self.errors_ignored = (options.fetch(:ignore, []) || []).to_a.compact
|
@@ -25,7 +31,11 @@ class LHC::Request
|
|
25
31
|
interceptors.intercept(:before_raw_request)
|
26
32
|
self.raw = create_request
|
27
33
|
interceptors.intercept(:before_request)
|
28
|
-
|
34
|
+
if self_executing && !response
|
35
|
+
run!
|
36
|
+
elsif response
|
37
|
+
on_complete(response)
|
38
|
+
end
|
29
39
|
end
|
30
40
|
|
31
41
|
def url
|
@@ -52,6 +62,23 @@ class LHC::Request
|
|
52
62
|
raw.run
|
53
63
|
end
|
54
64
|
|
65
|
+
def scrubbed_params
|
66
|
+
LHC::ParamsScrubber.new(params.deep_dup).scrubbed
|
67
|
+
end
|
68
|
+
|
69
|
+
def scrubbed_headers
|
70
|
+
LHC::HeadersScrubber.new(headers.deep_dup, options[:auth]).scrubbed
|
71
|
+
end
|
72
|
+
|
73
|
+
def scrubbed_options
|
74
|
+
scrubbed_options = options.deep_dup
|
75
|
+
scrubbed_options[:params] = LHC::ParamsScrubber.new(scrubbed_options[:params]).scrubbed
|
76
|
+
scrubbed_options[:headers] = LHC::HeadersScrubber.new(scrubbed_options[:headers], scrubbed_options[:auth]).scrubbed
|
77
|
+
scrubbed_options[:auth] = LHC::AuthScrubber.new(scrubbed_options[:auth]).scrubbed
|
78
|
+
scrubbed_options[:body] = LHC::BodyScrubber.new(scrubbed_options[:body]).scrubbed
|
79
|
+
scrubbed_options
|
80
|
+
end
|
81
|
+
|
55
82
|
private
|
56
83
|
|
57
84
|
attr_accessor :interceptors
|
@@ -63,6 +90,7 @@ class LHC::Request
|
|
63
90
|
|
64
91
|
def optionally_encoded_url(options)
|
65
92
|
return options[:url] unless options.fetch(:url_encoding, true)
|
93
|
+
|
66
94
|
encode_url(options[:url])
|
67
95
|
end
|
68
96
|
|
@@ -81,13 +109,15 @@ class LHC::Request
|
|
81
109
|
|
82
110
|
def translate_body(options)
|
83
111
|
return options if options.fetch(:body, nil).blank?
|
112
|
+
|
84
113
|
options[:body] = format.to_body(options[:body])
|
85
114
|
options
|
86
115
|
end
|
87
116
|
|
88
117
|
def encode_url(url)
|
89
118
|
return url if url.nil?
|
90
|
-
|
119
|
+
|
120
|
+
Addressable::URI.escape(url)
|
91
121
|
end
|
92
122
|
|
93
123
|
def typhoeusize(options)
|
@@ -96,6 +126,7 @@ class LHC::Request
|
|
96
126
|
options.delete(:url)
|
97
127
|
options.each do |key, _v|
|
98
128
|
next if TYPHOEUS_OPTIONS.include? key
|
129
|
+
|
99
130
|
method = "#{key}="
|
100
131
|
options.delete key unless easy.respond_to?(method)
|
101
132
|
end
|
@@ -107,6 +138,7 @@ class LHC::Request
|
|
107
138
|
def use_configured_endpoint!
|
108
139
|
endpoint = LHC.config.endpoints[options[:url]]
|
109
140
|
return unless endpoint
|
141
|
+
|
110
142
|
# explicit options override endpoint options
|
111
143
|
new_options = endpoint.options.deep_merge(options)
|
112
144
|
# set new options
|
@@ -128,13 +160,14 @@ class LHC::Request
|
|
128
160
|
end
|
129
161
|
|
130
162
|
def on_complete(response)
|
131
|
-
self.response = LHC::Response.new(response, self)
|
163
|
+
self.response = response.is_a?(LHC::Response) ? response : LHC::Response.new(response, self)
|
132
164
|
interceptors.intercept(:after_response)
|
133
165
|
handle_error(self.response) unless self.response.success?
|
134
166
|
end
|
135
167
|
|
136
168
|
def handle_error(response)
|
137
169
|
return if ignore_error?
|
170
|
+
|
138
171
|
throw_error(response) unless error_handler
|
139
172
|
response.body_replacement = error_handler.call(response)
|
140
173
|
end
|