dhc 1.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.
Files changed (185) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rubocop.yml +27 -0
  3. data/.github/workflows/test.yml +27 -0
  4. data/.gitignore +37 -0
  5. data/.rubocop.yml +105 -0
  6. data/.ruby-version +1 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE +674 -0
  9. data/README.md +999 -0
  10. data/Rakefile +25 -0
  11. data/dhc.gemspec +40 -0
  12. data/lib/core_ext/hash/deep_transform_values.rb +48 -0
  13. data/lib/dhc.rb +77 -0
  14. data/lib/dhc/concerns/dhc/basic_methods_concern.rb +42 -0
  15. data/lib/dhc/concerns/dhc/configuration_concern.rb +20 -0
  16. data/lib/dhc/concerns/dhc/fix_invalid_encoding_concern.rb +42 -0
  17. data/lib/dhc/concerns/dhc/formats_concern.rb +25 -0
  18. data/lib/dhc/concerns/dhc/request/user_agent_concern.rb +25 -0
  19. data/lib/dhc/config.rb +47 -0
  20. data/lib/dhc/endpoint.rb +119 -0
  21. data/lib/dhc/error.rb +82 -0
  22. data/lib/dhc/errors/client_error.rb +73 -0
  23. data/lib/dhc/errors/parser_error.rb +4 -0
  24. data/lib/dhc/errors/server_error.rb +28 -0
  25. data/lib/dhc/errors/timeout.rb +4 -0
  26. data/lib/dhc/errors/unknown_error.rb +4 -0
  27. data/lib/dhc/format.rb +18 -0
  28. data/lib/dhc/formats.rb +8 -0
  29. data/lib/dhc/formats/form.rb +45 -0
  30. data/lib/dhc/formats/json.rb +55 -0
  31. data/lib/dhc/formats/multipart.rb +45 -0
  32. data/lib/dhc/formats/plain.rb +42 -0
  33. data/lib/dhc/interceptor.rb +36 -0
  34. data/lib/dhc/interceptors.rb +26 -0
  35. data/lib/dhc/interceptors/auth.rb +94 -0
  36. data/lib/dhc/interceptors/caching.rb +148 -0
  37. data/lib/dhc/interceptors/default_timeout.rb +16 -0
  38. data/lib/dhc/interceptors/logging.rb +37 -0
  39. data/lib/dhc/interceptors/monitoring.rb +92 -0
  40. data/lib/dhc/interceptors/prometheus.rb +51 -0
  41. data/lib/dhc/interceptors/retry.rb +41 -0
  42. data/lib/dhc/interceptors/rollbar.rb +36 -0
  43. data/lib/dhc/interceptors/throttle.rb +86 -0
  44. data/lib/dhc/interceptors/zipkin.rb +110 -0
  45. data/lib/dhc/railtie.rb +9 -0
  46. data/lib/dhc/request.rb +161 -0
  47. data/lib/dhc/response.rb +60 -0
  48. data/lib/dhc/response/data.rb +28 -0
  49. data/lib/dhc/response/data/base.rb +18 -0
  50. data/lib/dhc/response/data/collection.rb +16 -0
  51. data/lib/dhc/response/data/item.rb +29 -0
  52. data/lib/dhc/rspec.rb +11 -0
  53. data/lib/dhc/test/cache_helper.rb +3 -0
  54. data/lib/dhc/version.rb +5 -0
  55. data/script/ci/build.sh +19 -0
  56. data/spec/basic_methods/delete_spec.rb +34 -0
  57. data/spec/basic_methods/get_spec.rb +49 -0
  58. data/spec/basic_methods/post_spec.rb +42 -0
  59. data/spec/basic_methods/put_spec.rb +48 -0
  60. data/spec/basic_methods/request_spec.rb +19 -0
  61. data/spec/basic_methods/request_without_rails_spec.rb +29 -0
  62. data/spec/config/endpoints_spec.rb +63 -0
  63. data/spec/config/placeholders_spec.rb +32 -0
  64. data/spec/core_ext/hash/deep_transform_values_spec.rb +24 -0
  65. data/spec/dummy/README.rdoc +28 -0
  66. data/spec/dummy/Rakefile +8 -0
  67. data/spec/dummy/app/assets/config/manifest.js +3 -0
  68. data/spec/dummy/app/assets/images/.keep +0 -0
  69. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  70. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  71. data/spec/dummy/app/controllers/application_controller.rb +7 -0
  72. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  73. data/spec/dummy/app/helpers/application_helper.rb +4 -0
  74. data/spec/dummy/app/mailers/.keep +0 -0
  75. data/spec/dummy/app/models/.keep +0 -0
  76. data/spec/dummy/app/models/concerns/.keep +0 -0
  77. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  78. data/spec/dummy/bin/bundle +5 -0
  79. data/spec/dummy/bin/rails +6 -0
  80. data/spec/dummy/bin/rake +6 -0
  81. data/spec/dummy/config.ru +6 -0
  82. data/spec/dummy/config/application.rb +16 -0
  83. data/spec/dummy/config/boot.rb +7 -0
  84. data/spec/dummy/config/environment.rb +7 -0
  85. data/spec/dummy/config/environments/development.rb +36 -0
  86. data/spec/dummy/config/environments/production.rb +77 -0
  87. data/spec/dummy/config/environments/test.rb +41 -0
  88. data/spec/dummy/config/initializers/assets.rb +10 -0
  89. data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
  90. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  91. data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  92. data/spec/dummy/config/initializers/inflections.rb +18 -0
  93. data/spec/dummy/config/initializers/mime_types.rb +6 -0
  94. data/spec/dummy/config/initializers/session_store.rb +5 -0
  95. data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
  96. data/spec/dummy/config/locales/en.yml +23 -0
  97. data/spec/dummy/config/routes.rb +58 -0
  98. data/spec/dummy/config/secrets.yml +22 -0
  99. data/spec/dummy/lib/assets/.keep +0 -0
  100. data/spec/dummy/log/.keep +0 -0
  101. data/spec/dummy/public/404.html +67 -0
  102. data/spec/dummy/public/422.html +67 -0
  103. data/spec/dummy/public/500.html +66 -0
  104. data/spec/dummy/public/favicon.ico +0 -0
  105. data/spec/endpoint/compile_spec.rb +35 -0
  106. data/spec/endpoint/match_spec.rb +41 -0
  107. data/spec/endpoint/placeholders_spec.rb +30 -0
  108. data/spec/endpoint/remove_interpolated_params_spec.rb +17 -0
  109. data/spec/endpoint/values_as_params_spec.rb +31 -0
  110. data/spec/error/dup_spec.rb +12 -0
  111. data/spec/error/find_spec.rb +57 -0
  112. data/spec/error/response_spec.rb +17 -0
  113. data/spec/error/timeout_spec.rb +14 -0
  114. data/spec/error/to_s_spec.rb +85 -0
  115. data/spec/formats/form_spec.rb +27 -0
  116. data/spec/formats/json_spec.rb +66 -0
  117. data/spec/formats/multipart_spec.rb +26 -0
  118. data/spec/formats/plain_spec.rb +29 -0
  119. data/spec/interceptors/after_request_spec.rb +20 -0
  120. data/spec/interceptors/after_response_spec.rb +39 -0
  121. data/spec/interceptors/auth/basic_auth_spec.rb +17 -0
  122. data/spec/interceptors/auth/bearer_spec.rb +19 -0
  123. data/spec/interceptors/auth/body_spec.rb +36 -0
  124. data/spec/interceptors/auth/long_basic_auth_credentials_spec.rb +17 -0
  125. data/spec/interceptors/auth/no_instance_var_for_options_spec.rb +27 -0
  126. data/spec/interceptors/auth/reauthentication_configuration_spec.rb +61 -0
  127. data/spec/interceptors/auth/reauthentication_spec.rb +44 -0
  128. data/spec/interceptors/before_request_spec.rb +21 -0
  129. data/spec/interceptors/before_response_spec.rb +20 -0
  130. data/spec/interceptors/caching/hydra_spec.rb +26 -0
  131. data/spec/interceptors/caching/main_spec.rb +73 -0
  132. data/spec/interceptors/caching/methods_spec.rb +42 -0
  133. data/spec/interceptors/caching/multilevel_cache_spec.rb +139 -0
  134. data/spec/interceptors/caching/options_spec.rb +78 -0
  135. data/spec/interceptors/caching/parameters_spec.rb +24 -0
  136. data/spec/interceptors/caching/response_status_spec.rb +29 -0
  137. data/spec/interceptors/caching/to_cache_spec.rb +16 -0
  138. data/spec/interceptors/default_interceptors_spec.rb +15 -0
  139. data/spec/interceptors/default_timeout/main_spec.rb +34 -0
  140. data/spec/interceptors/define_spec.rb +30 -0
  141. data/spec/interceptors/dup_spec.rb +19 -0
  142. data/spec/interceptors/logging/main_spec.rb +37 -0
  143. data/spec/interceptors/monitoring/caching_spec.rb +66 -0
  144. data/spec/interceptors/monitoring/main_spec.rb +97 -0
  145. data/spec/interceptors/prometheus_spec.rb +54 -0
  146. data/spec/interceptors/response_competition_spec.rb +39 -0
  147. data/spec/interceptors/retry/main_spec.rb +73 -0
  148. data/spec/interceptors/return_response_spec.rb +38 -0
  149. data/spec/interceptors/rollbar/invalid_encoding_spec.rb +43 -0
  150. data/spec/interceptors/rollbar/main_spec.rb +57 -0
  151. data/spec/interceptors/throttle/main_spec.rb +236 -0
  152. data/spec/interceptors/throttle/reset_track_spec.rb +53 -0
  153. data/spec/interceptors/zipkin/distributed_tracing_spec.rb +135 -0
  154. data/spec/rails_helper.rb +6 -0
  155. data/spec/request/body_spec.rb +39 -0
  156. data/spec/request/encoding_spec.rb +38 -0
  157. data/spec/request/error_handling_spec.rb +88 -0
  158. data/spec/request/headers_spec.rb +12 -0
  159. data/spec/request/ignore_errors_spec.rb +73 -0
  160. data/spec/request/option_dup_spec.rb +13 -0
  161. data/spec/request/parallel_requests_spec.rb +59 -0
  162. data/spec/request/params_encoding_spec.rb +26 -0
  163. data/spec/request/request_without_rails_spec.rb +15 -0
  164. data/spec/request/url_patterns_spec.rb +54 -0
  165. data/spec/request/user_agent_spec.rb +26 -0
  166. data/spec/request/user_agent_without_rails_spec.rb +27 -0
  167. data/spec/response/body_spec.rb +16 -0
  168. data/spec/response/code_spec.rb +16 -0
  169. data/spec/response/data_accessor_spec.rb +29 -0
  170. data/spec/response/data_spec.rb +85 -0
  171. data/spec/response/effective_url_spec.rb +16 -0
  172. data/spec/response/headers_spec.rb +18 -0
  173. data/spec/response/options_spec.rb +18 -0
  174. data/spec/response/success_spec.rb +13 -0
  175. data/spec/response/time_spec.rb +21 -0
  176. data/spec/spec_helper.rb +9 -0
  177. data/spec/support/fixtures/json/feedback.json +11 -0
  178. data/spec/support/fixtures/json/feedbacks.json +164 -0
  179. data/spec/support/fixtures/json/localina_content_ad.json +23 -0
  180. data/spec/support/load_json.rb +5 -0
  181. data/spec/support/reset_config.rb +8 -0
  182. data/spec/support/zipkin_mock.rb +114 -0
  183. data/spec/timeouts/no_signal_spec.rb +13 -0
  184. data/spec/timeouts/timings_spec.rb +55 -0
  185. metadata +527 -0
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DHC::Retry < DHC::Interceptor
4
+ attr_accessor :retries, :current_retry
5
+
6
+ class << self
7
+ attr_accessor :max, :all
8
+ end
9
+
10
+ def after_response
11
+ response.request.options[:retries] ||= 0
12
+ return unless retry?(response.request)
13
+ response.request.options[:retries] += 1
14
+ current_retry = response.request.options[:retries]
15
+ begin
16
+ response.request.run!
17
+ rescue DHC::Error
18
+ return
19
+ end
20
+ response.request.response if current_retry == response.request.options[:retries]
21
+ end
22
+
23
+ private
24
+
25
+ def retry?(request)
26
+ return false if request.response.success?
27
+ return false if request.error_ignored?
28
+ return false if !request.options.dig(:retry) && !DHC::Retry.all
29
+ request.options[:retries] < max(request)
30
+ end
31
+
32
+ def max(request)
33
+ options(request).is_a?(Hash) ? options(request).fetch(:max, DHC::Retry.max) : DHC::Retry.max
34
+ end
35
+
36
+ def options(request)
37
+ @options ||= request.options.dig(:retry)
38
+ end
39
+ end
40
+
41
+ DHC::Retry.max = 3
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'core_ext/hash/deep_transform_values'
4
+
5
+ class DHC::Rollbar < DHC::Interceptor
6
+ include ActiveSupport::Configurable
7
+ include DHC::FixInvalidEncodingConcern
8
+
9
+ def after_response
10
+ return unless Object.const_defined?('Rollbar')
11
+ return if response.success?
12
+ request = response.request
13
+ additional_params = request.options.fetch(:rollbar, {})
14
+ data = {
15
+ response: {
16
+ body: response.body,
17
+ code: response.code,
18
+ headers: response.headers,
19
+ time: response.time,
20
+ timeout?: response.timeout?
21
+ },
22
+ request: {
23
+ url: request.url,
24
+ method: request.method,
25
+ headers: request.headers,
26
+ params: request.params
27
+ }
28
+ }.merge additional_params
29
+ begin
30
+ Rollbar.warning("Status: #{response.code} URL: #{request.url}", data)
31
+ rescue Encoding::UndefinedConversionError
32
+ sanitized_data = data.deep_transform_values { |value| self.class.fix_invalid_encoding(value) }
33
+ Rollbar.warning("Status: #{response.code} URL: #{request.url}", sanitized_data)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/duration'
4
+
5
+ class DHC::Throttle < DHC::Interceptor
6
+ class OutOfQuota < StandardError
7
+ end
8
+
9
+ class << self
10
+ attr_accessor :track
11
+ end
12
+
13
+ def before_request
14
+ options = request.options.dig(:throttle)
15
+ return unless options
16
+ break_options = options.dig(:break)
17
+ return unless break_options
18
+ break_when_quota_reached! if break_options.match('%')
19
+ end
20
+
21
+ def after_response
22
+ options = response.request.options.dig(:throttle)
23
+ return unless throttle?(options)
24
+ self.class.track ||= {}
25
+ self.class.track[options.dig(:provider)] = {
26
+ limit: limit(options: options[:limit], response: response),
27
+ remaining: remaining(options: options[:remaining], response: response),
28
+ expires: expires(options: options[:expires], response: response)
29
+ }
30
+ end
31
+
32
+ private
33
+
34
+ def throttle?(options)
35
+ [options&.dig(:track), response.headers].none?(&:blank?)
36
+ end
37
+
38
+ def break_when_quota_reached!
39
+ options = request.options.dig(:throttle)
40
+ track = (self.class.track || {}).dig(options[:provider])
41
+ return if track.blank? || track[:remaining].blank? || track[:limit].blank? || track[:expires].blank?
42
+ return if Time.zone.now > track[:expires]
43
+ # avoid floats by multiplying with 100
44
+ remaining = track[:remaining] * 100
45
+ limit = track[:limit]
46
+ quota = 100 - options[:break].to_i
47
+ raise(OutOfQuota, "Reached predefined quota for #{options[:provider]}") if remaining < quota * limit
48
+ end
49
+
50
+ def limit(options:, response:)
51
+ @limit ||=
52
+ if options.is_a?(Proc)
53
+ options.call(response)
54
+ elsif options.is_a?(Integer)
55
+ options
56
+ elsif options.is_a?(Hash) && options[:header]
57
+ response.headers[options[:header]]&.to_i
58
+ end
59
+ end
60
+
61
+ def remaining(options:, response:)
62
+ @remaining ||=
63
+ begin
64
+ if options.is_a?(Proc)
65
+ options.call(response)
66
+ elsif options.is_a?(Hash) && options[:header]
67
+ response.headers[options[:header]]&.to_i
68
+ end
69
+ end
70
+ end
71
+
72
+ def expires(options:, response:)
73
+ @expires ||= convert_expires(read_expire_option(options, response))
74
+ end
75
+
76
+ def read_expire_option(options, response)
77
+ (options.is_a?(Hash) && options[:header]) ? response.headers[options[:header]] : options
78
+ end
79
+
80
+ def convert_expires(value)
81
+ return if value.blank?
82
+ return value.call(response) if value.is_a?(Proc)
83
+ return Time.parse(value) if value.match(/GMT/)
84
+ Time.zone.at(value.to_i).to_datetime
85
+ end
86
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DHC::Zipkin < DHC::Interceptor
4
+ B3_HEADERS = {
5
+ trace_id: 'X-B3-TraceId',
6
+ parent_id: 'X-B3-ParentSpanId',
7
+ span_id: 'X-B3-SpanId',
8
+ sampled: 'X-B3-Sampled',
9
+ flags: 'X-B3-Flags'
10
+ }.freeze
11
+ TRUE = '1' # true in binary annotation
12
+
13
+ def before_request
14
+ return if !dependencies? || !tracing?
15
+ ZipkinTracer::TraceContainer.with_trace_id(trace_id) do
16
+ # add headers even if the current trace_id should not be sampled
17
+ B3_HEADERS.each { |method, header| request.headers[header] = trace_id.send(method).to_s }
18
+ # only sample the current call if we're instructed to do so
19
+ start_trace! if ::Trace.tracer && trace_id.sampled?
20
+ end
21
+ end
22
+
23
+ def after_response
24
+ # only sample the current call if we're instructed to do so
25
+ return unless dependencies? && trace_id.sampled?
26
+ end_trace!
27
+ end
28
+
29
+ private
30
+
31
+ def tracing?
32
+ ZipkinTracer::TraceContainer.current
33
+ end
34
+
35
+ def start_trace!
36
+ record_path
37
+ record_remote_endpoint
38
+ record_local_endpoint
39
+ end
40
+
41
+ def end_trace!
42
+ record_status
43
+ record_error unless response.success?
44
+ record_end
45
+ end
46
+
47
+ def trace_id
48
+ @trace_id ||= ZipkinTracer::TraceGenerator.new.next_trace_id
49
+ end
50
+
51
+ # register a new span with zipkin tracer
52
+ def span
53
+ @span ||= ::Trace.tracer.start_span(trace_id, url.path)
54
+ end
55
+
56
+ def url
57
+ @url ||= URI(request.raw.url)
58
+ end
59
+
60
+ def status
61
+ @status ||= response.code.to_s
62
+ end
63
+
64
+ def service_name
65
+ @service_name ||= url.host
66
+ end
67
+
68
+ def record_local_endpoint
69
+ span.record(::Trace::Annotation::CLIENT_SEND, local_endpoint)
70
+ end
71
+
72
+ def record_remote_endpoint
73
+ span.record_tag(::Trace::BinaryAnnotation::SERVER_ADDRESS, TRUE, ::Trace::BinaryAnnotation::Type::BOOL, remote_endpoint)
74
+ end
75
+
76
+ def record_path
77
+ span.record_tag(::Trace::BinaryAnnotation::PATH, url.path, ::Trace::BinaryAnnotation::Type::STRING, local_endpoint)
78
+ end
79
+
80
+ def record_end
81
+ span.record(::Trace::Annotation::CLIENT_RECV, local_endpoint)
82
+ ::Trace.tracer.end_span(span)
83
+ end
84
+
85
+ def record_error
86
+ span.record_tag(::Trace::BinaryAnnotation::ERROR, status, ::Trace::BinaryAnnotation::Type::STRING, local_endpoint)
87
+ end
88
+
89
+ def record_status
90
+ span.record_tag(::Trace::BinaryAnnotation::STATUS, status, ::Trace::BinaryAnnotation::Type::STRING, local_endpoint)
91
+ end
92
+
93
+ def local_endpoint
94
+ @local_endpoint ||= ::Trace.default_endpoint
95
+ end
96
+
97
+ def remote_endpoint
98
+ @remote_endpoint ||= ::Trace::Endpoint.remote_endpoint(url, service_name, local_endpoint.ip_format)
99
+ end
100
+
101
+ def dependencies?
102
+ (
103
+ defined?(ZipkinTracer::TraceContainer) &&
104
+ defined?(::Trace) &&
105
+ defined?(::Trace::Annotation) &&
106
+ defined?(::Trace::BinaryAnnotation) &&
107
+ defined?(::Trace::Endpoint)
108
+ ) || warn('[WARNING] Zipkin interceptor is enabled but dependencies are not found. See: https://github.com/DePayFi/dhc#zipkin')
109
+ end
110
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DHC
4
+ class Railtie < Rails::Railtie
5
+ initializer 'dhc.configure_rails_initialization' do
6
+ DHC::Caching.cache ||= Rails.cache
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'active_support/core_ext/object/deep_dup'
5
+ require 'dhc/concerns/dhc/request/user_agent_concern'
6
+
7
+ # The request is doing an http-request using typhoeus.
8
+ # It provides functionalities to access and alter request data
9
+ # and it communicates with interceptors.
10
+ class DHC::Request
11
+ include UserAgentConcern
12
+
13
+ TYPHOEUS_OPTIONS ||= %i[params method body headers follow_location params_encoding].freeze
14
+
15
+ attr_accessor :response, :options, :raw, :format, :error_handler, :errors_ignored, :source
16
+
17
+ def initialize(options, self_executing = true)
18
+ self.errors_ignored = (options.fetch(:ignore, []) || []).to_a.compact
19
+ self.source = options&.dig(:source)
20
+ self.options = format!(options.deep_dup || {})
21
+ self.error_handler = options.delete :rescue
22
+ use_configured_endpoint!
23
+ generate_url_from_template!
24
+ self.interceptors = DHC::Interceptors.new(self)
25
+ interceptors.intercept(:before_raw_request)
26
+ self.raw = create_request
27
+ interceptors.intercept(:before_request)
28
+ if self_executing && !response
29
+ run!
30
+ elsif response
31
+ on_complete(response)
32
+ end
33
+ end
34
+
35
+ def url
36
+ raw.base_url || options[:url]
37
+ end
38
+
39
+ def method
40
+ (raw.options[:method] || options[:method] || :get).to_sym
41
+ end
42
+
43
+ def headers
44
+ raw.options.fetch(:headers, nil) || raw.options[:headers] = {}
45
+ end
46
+
47
+ def params
48
+ raw.options.fetch(:params, nil) || raw.options[:params] = {}
49
+ end
50
+
51
+ def error_ignored?
52
+ ignore_error?
53
+ end
54
+
55
+ def run!
56
+ raw.run
57
+ end
58
+
59
+ private
60
+
61
+ attr_accessor :interceptors
62
+
63
+ def format!(options)
64
+ self.format = options.delete(:format) || DHC::Formats::JSON.new
65
+ format.format_options(options)
66
+ end
67
+
68
+ def optionally_encoded_url(options)
69
+ return options[:url] unless options.fetch(:url_encoding, true)
70
+ encode_url(options[:url])
71
+ end
72
+
73
+ def create_request
74
+ request = Typhoeus::Request.new(
75
+ optionally_encoded_url(options),
76
+ translate_body(typhoeusize(options))
77
+ )
78
+ request.on_headers do
79
+ interceptors.intercept(:after_request)
80
+ interceptors.intercept(:before_response)
81
+ end
82
+ request.on_complete { |response| on_complete(response) }
83
+ request
84
+ end
85
+
86
+ def translate_body(options)
87
+ return options if options.fetch(:body, nil).blank?
88
+ options[:body] = format.to_body(options[:body])
89
+ options
90
+ end
91
+
92
+ def encode_url(url)
93
+ return url if url.nil?
94
+ Addressable::URI.escape(url)
95
+ end
96
+
97
+ def typhoeusize(options)
98
+ options = options.deep_dup
99
+ easy = Ethon::Easy.new
100
+ options.delete(:url)
101
+ options.each do |key, _v|
102
+ next if TYPHOEUS_OPTIONS.include? key
103
+ method = "#{key}="
104
+ options.delete key unless easy.respond_to?(method)
105
+ end
106
+ options
107
+ end
108
+
109
+ # Get configured endpoint and use it for doing the request.
110
+ # Explicit request options are overriding configured options.
111
+ def use_configured_endpoint!
112
+ endpoint = DHC.config.endpoints[options[:url]]
113
+ return unless endpoint
114
+ # explicit options override endpoint options
115
+ new_options = endpoint.options.deep_merge(options)
116
+ # set new options
117
+ self.options = new_options
118
+ options[:url] = endpoint.url
119
+ end
120
+
121
+ # Generates URL from a URL template
122
+ def generate_url_from_template!
123
+ endpoint = DHC::Endpoint.new(options[:url])
124
+ params =
125
+ if format && options[:body].present? && options[:body].respond_to?(:as_json) && options[:body].as_json.is_a?(Hash)
126
+ options[:body].as_json.merge(options[:params] || {}).deep_symbolize_keys
127
+ else
128
+ options[:params]
129
+ end
130
+ options[:url] = endpoint.compile(params)
131
+ endpoint.remove_interpolated_params!(options[:params])
132
+ end
133
+
134
+ def on_complete(response)
135
+ self.response = response.is_a?(DHC::Response) ? response : DHC::Response.new(response, self)
136
+ interceptors.intercept(:after_response)
137
+ handle_error(self.response) unless self.response.success?
138
+ end
139
+
140
+ def handle_error(response)
141
+ return if ignore_error?
142
+ throw_error(response) unless error_handler
143
+ response.body_replacement = error_handler.call(response)
144
+ end
145
+
146
+ def ignore_error?
147
+ @ignore_error ||= begin
148
+ errors_ignored.detect do |ignored_error|
149
+ error <= ignored_error
150
+ end.present?
151
+ end
152
+ end
153
+
154
+ def error
155
+ @error ||= DHC::Error.find(response)
156
+ end
157
+
158
+ def throw_error(response)
159
+ raise error.new(error, response)
160
+ end
161
+ end