lhc 12.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 (195) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +37 -0
  3. data/.rubocop.localch.yml +325 -0
  4. data/.rubocop.yml +61 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +13 -0
  7. data/Gemfile.activesupport4 +4 -0
  8. data/Gemfile.activesupport5 +4 -0
  9. data/Gemfile.activesupport6 +4 -0
  10. data/LICENSE +674 -0
  11. data/README.md +984 -0
  12. data/Rakefile +25 -0
  13. data/cider-ci.yml +6 -0
  14. data/cider-ci/bin/bundle +51 -0
  15. data/cider-ci/bin/ruby_install +8 -0
  16. data/cider-ci/bin/ruby_version +25 -0
  17. data/cider-ci/jobs/rspec-activesupport-4.yml +28 -0
  18. data/cider-ci/jobs/rspec-activesupport-5.yml +27 -0
  19. data/cider-ci/jobs/rspec-activesupport-6.yml +28 -0
  20. data/cider-ci/jobs/rubocop.yml +18 -0
  21. data/cider-ci/task_components/bundle.yml +22 -0
  22. data/cider-ci/task_components/rspec.yml +36 -0
  23. data/cider-ci/task_components/rubocop.yml +29 -0
  24. data/cider-ci/task_components/ruby.yml +15 -0
  25. data/friday.yml +3 -0
  26. data/lhc.gemspec +39 -0
  27. data/lib/core_ext/hash/deep_transform_values.rb +48 -0
  28. data/lib/lhc.rb +136 -0
  29. data/lib/lhc/concerns/lhc/basic_methods_concern.rb +42 -0
  30. data/lib/lhc/concerns/lhc/configuration_concern.rb +20 -0
  31. data/lib/lhc/concerns/lhc/fix_invalid_encoding_concern.rb +42 -0
  32. data/lib/lhc/concerns/lhc/formats_concern.rb +25 -0
  33. data/lib/lhc/concerns/lhc/request/user_agent_concern.rb +25 -0
  34. data/lib/lhc/config.rb +47 -0
  35. data/lib/lhc/endpoint.rb +119 -0
  36. data/lib/lhc/error.rb +80 -0
  37. data/lib/lhc/errors/client_error.rb +73 -0
  38. data/lib/lhc/errors/parser_error.rb +4 -0
  39. data/lib/lhc/errors/server_error.rb +28 -0
  40. data/lib/lhc/errors/timeout.rb +4 -0
  41. data/lib/lhc/errors/unknown_error.rb +4 -0
  42. data/lib/lhc/format.rb +18 -0
  43. data/lib/lhc/formats.rb +8 -0
  44. data/lib/lhc/formats/form.rb +45 -0
  45. data/lib/lhc/formats/json.rb +55 -0
  46. data/lib/lhc/formats/multipart.rb +45 -0
  47. data/lib/lhc/formats/plain.rb +42 -0
  48. data/lib/lhc/interceptor.rb +32 -0
  49. data/lib/lhc/interceptors.rb +26 -0
  50. data/lib/lhc/interceptors/auth.rb +98 -0
  51. data/lib/lhc/interceptors/caching.rb +127 -0
  52. data/lib/lhc/interceptors/default_timeout.rb +16 -0
  53. data/lib/lhc/interceptors/logging.rb +37 -0
  54. data/lib/lhc/interceptors/monitoring.rb +63 -0
  55. data/lib/lhc/interceptors/prometheus.rb +51 -0
  56. data/lib/lhc/interceptors/retry.rb +41 -0
  57. data/lib/lhc/interceptors/rollbar.rb +36 -0
  58. data/lib/lhc/interceptors/throttle.rb +81 -0
  59. data/lib/lhc/interceptors/zipkin.rb +110 -0
  60. data/lib/lhc/railtie.rb +10 -0
  61. data/lib/lhc/request.rb +157 -0
  62. data/lib/lhc/response.rb +60 -0
  63. data/lib/lhc/response/data.rb +28 -0
  64. data/lib/lhc/response/data/base.rb +22 -0
  65. data/lib/lhc/response/data/collection.rb +16 -0
  66. data/lib/lhc/response/data/item.rb +29 -0
  67. data/lib/lhc/rspec.rb +12 -0
  68. data/lib/lhc/test/cache_helper.rb +3 -0
  69. data/lib/lhc/version.rb +5 -0
  70. data/script/ci/build.sh +19 -0
  71. data/spec/basic_methods/delete_spec.rb +34 -0
  72. data/spec/basic_methods/get_spec.rb +49 -0
  73. data/spec/basic_methods/post_spec.rb +42 -0
  74. data/spec/basic_methods/put_spec.rb +48 -0
  75. data/spec/basic_methods/request_spec.rb +19 -0
  76. data/spec/basic_methods/request_without_rails_spec.rb +29 -0
  77. data/spec/config/endpoints_spec.rb +63 -0
  78. data/spec/config/placeholders_spec.rb +32 -0
  79. data/spec/core_ext/hash/deep_transform_values_spec.rb +24 -0
  80. data/spec/dummy/README.rdoc +28 -0
  81. data/spec/dummy/Rakefile +8 -0
  82. data/spec/dummy/app/assets/config/manifest.js +3 -0
  83. data/spec/dummy/app/assets/images/.keep +0 -0
  84. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  85. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  86. data/spec/dummy/app/controllers/application_controller.rb +7 -0
  87. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  88. data/spec/dummy/app/helpers/application_helper.rb +4 -0
  89. data/spec/dummy/app/mailers/.keep +0 -0
  90. data/spec/dummy/app/models/.keep +0 -0
  91. data/spec/dummy/app/models/concerns/.keep +0 -0
  92. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  93. data/spec/dummy/bin/bundle +5 -0
  94. data/spec/dummy/bin/rails +6 -0
  95. data/spec/dummy/bin/rake +6 -0
  96. data/spec/dummy/config.ru +6 -0
  97. data/spec/dummy/config/application.rb +16 -0
  98. data/spec/dummy/config/boot.rb +7 -0
  99. data/spec/dummy/config/environment.rb +7 -0
  100. data/spec/dummy/config/environments/development.rb +36 -0
  101. data/spec/dummy/config/environments/production.rb +77 -0
  102. data/spec/dummy/config/environments/test.rb +41 -0
  103. data/spec/dummy/config/initializers/assets.rb +10 -0
  104. data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
  105. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  106. data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  107. data/spec/dummy/config/initializers/inflections.rb +18 -0
  108. data/spec/dummy/config/initializers/mime_types.rb +6 -0
  109. data/spec/dummy/config/initializers/session_store.rb +5 -0
  110. data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
  111. data/spec/dummy/config/locales/en.yml +23 -0
  112. data/spec/dummy/config/routes.rb +58 -0
  113. data/spec/dummy/config/secrets.yml +22 -0
  114. data/spec/dummy/lib/assets/.keep +0 -0
  115. data/spec/dummy/log/.keep +0 -0
  116. data/spec/dummy/public/404.html +67 -0
  117. data/spec/dummy/public/422.html +67 -0
  118. data/spec/dummy/public/500.html +66 -0
  119. data/spec/dummy/public/favicon.ico +0 -0
  120. data/spec/endpoint/compile_spec.rb +35 -0
  121. data/spec/endpoint/match_spec.rb +41 -0
  122. data/spec/endpoint/placeholders_spec.rb +30 -0
  123. data/spec/endpoint/remove_interpolated_params_spec.rb +17 -0
  124. data/spec/endpoint/values_as_params_spec.rb +31 -0
  125. data/spec/error/dup_spec.rb +12 -0
  126. data/spec/error/find_spec.rb +57 -0
  127. data/spec/error/response_spec.rb +17 -0
  128. data/spec/error/timeout_spec.rb +14 -0
  129. data/spec/error/to_s_spec.rb +80 -0
  130. data/spec/formats/form_spec.rb +27 -0
  131. data/spec/formats/json_spec.rb +66 -0
  132. data/spec/formats/multipart_spec.rb +26 -0
  133. data/spec/formats/plain_spec.rb +29 -0
  134. data/spec/interceptors/after_request_spec.rb +20 -0
  135. data/spec/interceptors/after_response_spec.rb +39 -0
  136. data/spec/interceptors/auth/basic_auth_spec.rb +17 -0
  137. data/spec/interceptors/auth/bearer_spec.rb +19 -0
  138. data/spec/interceptors/auth/reauthentication_configuration_spec.rb +61 -0
  139. data/spec/interceptors/auth/reauthentication_spec.rb +44 -0
  140. data/spec/interceptors/before_request_spec.rb +21 -0
  141. data/spec/interceptors/before_response_spec.rb +20 -0
  142. data/spec/interceptors/caching/hydra_spec.rb +26 -0
  143. data/spec/interceptors/caching/main_spec.rb +73 -0
  144. data/spec/interceptors/caching/methods_spec.rb +42 -0
  145. data/spec/interceptors/caching/options_spec.rb +89 -0
  146. data/spec/interceptors/caching/parameters_spec.rb +24 -0
  147. data/spec/interceptors/caching/response_status_spec.rb +29 -0
  148. data/spec/interceptors/caching/to_cache_spec.rb +16 -0
  149. data/spec/interceptors/default_interceptors_spec.rb +15 -0
  150. data/spec/interceptors/default_timeout/main_spec.rb +34 -0
  151. data/spec/interceptors/define_spec.rb +29 -0
  152. data/spec/interceptors/dup_spec.rb +19 -0
  153. data/spec/interceptors/logging/main_spec.rb +37 -0
  154. data/spec/interceptors/monitoring/main_spec.rb +97 -0
  155. data/spec/interceptors/prometheus_spec.rb +54 -0
  156. data/spec/interceptors/response_competition_spec.rb +41 -0
  157. data/spec/interceptors/retry/main_spec.rb +73 -0
  158. data/spec/interceptors/return_response_spec.rb +38 -0
  159. data/spec/interceptors/rollbar/invalid_encoding_spec.rb +43 -0
  160. data/spec/interceptors/rollbar/main_spec.rb +57 -0
  161. data/spec/interceptors/throttle/main_spec.rb +106 -0
  162. data/spec/interceptors/throttle/reset_track_spec.rb +53 -0
  163. data/spec/interceptors/zipkin/distributed_tracing_spec.rb +135 -0
  164. data/spec/rails_helper.rb +6 -0
  165. data/spec/request/body_spec.rb +39 -0
  166. data/spec/request/encoding_spec.rb +37 -0
  167. data/spec/request/error_handling_spec.rb +88 -0
  168. data/spec/request/headers_spec.rb +12 -0
  169. data/spec/request/ignore_errors_spec.rb +73 -0
  170. data/spec/request/option_dup_spec.rb +13 -0
  171. data/spec/request/parallel_requests_spec.rb +59 -0
  172. data/spec/request/params_encoding_spec.rb +26 -0
  173. data/spec/request/request_without_rails_spec.rb +15 -0
  174. data/spec/request/url_patterns_spec.rb +54 -0
  175. data/spec/request/user_agent_spec.rb +26 -0
  176. data/spec/request/user_agent_without_rails_spec.rb +27 -0
  177. data/spec/response/body_spec.rb +16 -0
  178. data/spec/response/code_spec.rb +16 -0
  179. data/spec/response/data_accessor_spec.rb +29 -0
  180. data/spec/response/data_spec.rb +61 -0
  181. data/spec/response/effective_url_spec.rb +16 -0
  182. data/spec/response/headers_spec.rb +18 -0
  183. data/spec/response/options_spec.rb +18 -0
  184. data/spec/response/success_spec.rb +13 -0
  185. data/spec/response/time_spec.rb +21 -0
  186. data/spec/spec_helper.rb +8 -0
  187. data/spec/support/fixtures/json/feedback.json +11 -0
  188. data/spec/support/fixtures/json/feedbacks.json +164 -0
  189. data/spec/support/fixtures/json/localina_content_ad.json +23 -0
  190. data/spec/support/load_json.rb +5 -0
  191. data/spec/support/reset_config.rb +7 -0
  192. data/spec/support/zipkin_mock.rb +113 -0
  193. data/spec/timeouts/no_signal_spec.rb +13 -0
  194. data/spec/timeouts/timings_spec.rb +55 -0
  195. metadata +534 -0
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LHC::ParserError < LHC::Error
4
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LHC::ServerError < LHC::Error
4
+ end
5
+
6
+ class LHC::InternalServerError < LHC::ServerError
7
+ end
8
+
9
+ class LHC::NotImplemented < LHC::ServerError
10
+ end
11
+
12
+ class LHC::BadGateway < LHC::ServerError
13
+ end
14
+
15
+ class LHC::ServiceUnavailable < LHC::ServerError
16
+ end
17
+
18
+ class LHC::GatewayTimeout < LHC::ServerError
19
+ end
20
+
21
+ class LHC::HttpVersionNotSupported < LHC::ServerError
22
+ end
23
+
24
+ class LHC::InsufficientStorage < LHC::ServerError
25
+ end
26
+
27
+ class LHC::NotExtended < LHC::ServerError
28
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LHC::Timeout < LHC::Error
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LHC::UnknownError < LHC::Error
4
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LHC::Format
4
+
5
+ private
6
+
7
+ def no_content_type_header!(options)
8
+ return if (options[:headers].keys & [:'Content-Type', 'Content-Type']).blank?
9
+
10
+ raise 'Content-Type header is not allowed for formatted requests!\nSee https://github.com/local-ch/lhc#formats for more information.'
11
+ end
12
+
13
+ def no_accept_header!(options)
14
+ return if (options[:headers].keys & [:Accept, 'Accept']).blank?
15
+
16
+ raise 'Accept header is not allowed for formatted requests!\nSee https://github.com/local-ch/lhc#formats for more information.'
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LHC::Formats
4
+ autoload :Form, 'lhc/formats/form'
5
+ autoload :JSON, 'lhc/formats/json'
6
+ autoload :Multipart, 'lhc/formats/multipart'
7
+ autoload :Plain, 'lhc/formats/plain'
8
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LHC::Formats
4
+ class Form < LHC::Format
5
+ include LHC::BasicMethodsConcern
6
+
7
+ def self.request(options)
8
+ options[:format] = new
9
+ super(options)
10
+ end
11
+
12
+ def format_options(options)
13
+ options[:headers] ||= {}
14
+ no_content_type_header!(options)
15
+ options[:headers]['Content-Type'] = 'application/x-www-form-urlencoded'
16
+ options
17
+ end
18
+
19
+ def as_json(input)
20
+ parse(input)
21
+ end
22
+
23
+ def as_open_struct(input)
24
+ parse(input)
25
+ end
26
+
27
+ def to_body(input)
28
+ input
29
+ end
30
+
31
+ def to_s
32
+ 'form'
33
+ end
34
+
35
+ def to_sym
36
+ to_s.to_sym
37
+ end
38
+
39
+ private
40
+
41
+ def parse(input)
42
+ input
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LHC::Formats
4
+ class JSON < LHC::Format
5
+ include LHC::BasicMethodsConcern
6
+
7
+ def self.request(options)
8
+ options[:format] = new
9
+ super(options)
10
+ end
11
+
12
+ def format_options(options)
13
+ options[:headers] ||= {}
14
+ no_content_type_header!(options)
15
+ no_accept_header!(options)
16
+
17
+ options[:headers]['Content-Type'] = 'application/json; charset=utf-8'
18
+ options[:headers]['Accept'] = 'application/json,application/vnd.api+json'
19
+ options[:headers]['Accept-Charset'] = 'utf-8'
20
+ options
21
+ end
22
+
23
+ def as_json(input)
24
+ parse(input, Hash)
25
+ end
26
+
27
+ def as_open_struct(input)
28
+ parse(input, OpenStruct)
29
+ end
30
+
31
+ def to_body(input)
32
+ if input.is_a?(String)
33
+ input
34
+ else
35
+ input.to_json
36
+ end
37
+ end
38
+
39
+ def to_s
40
+ 'json'
41
+ end
42
+
43
+ def to_sym
44
+ to_s.to_sym
45
+ end
46
+
47
+ private
48
+
49
+ def parse(input, object_class)
50
+ ::JSON.parse(input, object_class: object_class)
51
+ rescue ::JSON::ParserError => e
52
+ raise LHC::ParserError.new(e.message, input)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LHC::Formats
4
+ class Multipart < LHC::Format
5
+ include LHC::BasicMethodsConcern
6
+
7
+ def self.request(options)
8
+ options[:format] = new
9
+ super(options)
10
+ end
11
+
12
+ def format_options(options)
13
+ options[:headers] ||= {}
14
+ no_content_type_header!(options)
15
+ options[:headers]['Content-Type'] = 'multipart/form-data'
16
+ options
17
+ end
18
+
19
+ def as_json(input)
20
+ parse(input)
21
+ end
22
+
23
+ def as_open_struct(input)
24
+ parse(input)
25
+ end
26
+
27
+ def to_body(input)
28
+ input
29
+ end
30
+
31
+ def to_s
32
+ 'multipart'
33
+ end
34
+
35
+ def to_sym
36
+ to_s.to_sym
37
+ end
38
+
39
+ private
40
+
41
+ def parse(input)
42
+ input
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LHC::Formats
4
+ class Plain < LHC::Format
5
+ include LHC::BasicMethodsConcern
6
+
7
+ def self.request(options)
8
+ options[:format] = new
9
+ super(options)
10
+ end
11
+
12
+ def format_options(options)
13
+ options
14
+ end
15
+
16
+ def as_json(input)
17
+ parse(input)
18
+ end
19
+
20
+ def as_open_struct(input)
21
+ parse(input)
22
+ end
23
+
24
+ def to_body(input)
25
+ input
26
+ end
27
+
28
+ def to_s
29
+ 'plain'
30
+ end
31
+
32
+ def to_sym
33
+ to_s.to_sym
34
+ end
35
+
36
+ private
37
+
38
+ def parse(input)
39
+ input
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LHC::Interceptor
4
+
5
+ attr_reader :request
6
+
7
+ def initialize(request)
8
+ @request = request
9
+ end
10
+
11
+ def response
12
+ @request.response
13
+ end
14
+
15
+ def before_raw_request; end
16
+
17
+ def before_request; end
18
+
19
+ def after_request; end
20
+
21
+ def before_response; end
22
+
23
+ def after_response; end
24
+
25
+ # Prevent Interceptors from beeing duplicated!
26
+ # Their classes have flag-character.
27
+ # When duplicated you can't check for their class name anymore:
28
+ # e.g. options.deep_dup[:interceptors].include?(LHC::Caching) # false
29
+ def self.dup
30
+ self
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Handles interceptions during the lifecycle of a request
4
+ # Represents all active interceptors for a request/response.
5
+ class LHC::Interceptors
6
+
7
+ attr_accessor :all
8
+
9
+ # Intitalizes and determines if global or local interceptors are used
10
+ def initialize(request)
11
+ self.all = (request.options[:interceptors] || LHC.config.interceptors).map do |interceptor|
12
+ interceptor.new(request)
13
+ end
14
+ end
15
+
16
+ # Forwards messages to interceptors and handles provided responses.
17
+ def intercept(name)
18
+ all.each do |interceptor|
19
+ result = interceptor.send(name)
20
+ if result.is_a? LHC::Response
21
+ raise 'Response already set from another interceptor' if @response
22
+ @response = interceptor.request.response = result
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LHC::Auth < LHC::Interceptor
4
+ include ActiveSupport::Configurable
5
+ config_accessor :refresh_client_token
6
+
7
+ def before_request
8
+ authenticate!
9
+ end
10
+
11
+ def after_response
12
+ return unless configuration_correct?
13
+ return unless reauthenticate?
14
+ reauthenticate!
15
+ end
16
+
17
+ private
18
+
19
+ def authenticate!
20
+ if auth_options[:bearer]
21
+ bearer_authentication!
22
+ elsif auth_options[:basic]
23
+ basic_authentication!
24
+ end
25
+ end
26
+
27
+ def basic_authentication!
28
+ auth = auth_options[:basic]
29
+ credentials = "#{auth[:username]}:#{auth[:password]}"
30
+ set_authorization_header("Basic #{Base64.encode64(credentials).chomp}")
31
+ end
32
+
33
+ def bearer_authentication!
34
+ token = auth_options[:bearer]
35
+ token = token.call if token.is_a?(Proc)
36
+ set_bearer_authorization_header(token)
37
+ end
38
+
39
+ # rubocop:disable Style/AccessorMethodName
40
+ def set_authorization_header(value)
41
+ request.headers['Authorization'] = value
42
+ end
43
+
44
+ def set_bearer_authorization_header(token)
45
+ set_authorization_header("Bearer #{token}")
46
+ end
47
+ # rubocop:enable Style/AccessorMethodName
48
+
49
+ def reauthenticate!
50
+ # refresh token and update header
51
+ token = refresh_client_token_option.call
52
+ set_bearer_authorization_header(token)
53
+ # trigger LHC::Retry and ensure we do not trigger reauthenticate!
54
+ # again should it fail another time
55
+ new_options = request.options.dup
56
+ new_options = new_options.merge(retry: { max: 1 })
57
+ new_options = new_options.merge(auth: { reauthenticated: true })
58
+ request.options = new_options
59
+ end
60
+
61
+ def reauthenticate?
62
+ !response.success? &&
63
+ !auth_options[:reauthenticated] &&
64
+ bearer_header_present? &&
65
+ LHC::Error.find(response) == LHC::Unauthorized
66
+ end
67
+
68
+ def bearer_header_present?
69
+ @has_bearer_header ||= request.headers['Authorization'] =~ /^Bearer .+$/i
70
+ end
71
+
72
+ def refresh_client_token_option
73
+ @refresh_client_token_option ||= auth_options[:refresh_client_token] || refresh_client_token
74
+ end
75
+
76
+ def all_interceptor_classes
77
+ @all_interceptors ||= LHC::Interceptors.new(request).all.map(&:class)
78
+ end
79
+
80
+ def auth_options
81
+ @auth_options ||= request.options[:auth].dup || {}
82
+ end
83
+
84
+ def configuration_correct?
85
+ # warn user about configs, only if refresh_client_token_option is set at all
86
+ refresh_client_token_option && refresh_client_token? && retry_interceptor?
87
+ end
88
+
89
+ def refresh_client_token?
90
+ return true if refresh_client_token_option.is_a?(Proc)
91
+ warn("[WARNING] The given refresh_client_token must be a Proc for reauthentication.")
92
+ end
93
+
94
+ def retry_interceptor?
95
+ return true if all_interceptor_classes.include?(LHC::Retry) && all_interceptor_classes.index(LHC::Retry) > all_interceptor_classes.index(self.class)
96
+ warn("[WARNING] Your interceptors must include LHC::Retry after LHC::Auth.")
97
+ end
98
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LHC::Caching < LHC::Interceptor
4
+ include ActiveSupport::Configurable
5
+
6
+ config_accessor :cache, :logger
7
+
8
+ CACHE_VERSION = '1'
9
+
10
+ # Options forwarded to the cache
11
+ FORWARDED_OPTIONS = [:expires_in, :race_condition_ttl]
12
+
13
+ def before_request
14
+ return unless cache?(request)
15
+ deprecation_warning(request.options)
16
+ options = options(request.options)
17
+ key = key(request, options[:key])
18
+ response_data = cache_for(options).fetch(key)
19
+ return unless response_data
20
+ logger&.info "Served from cache: #{key}"
21
+ from_cache(request, response_data)
22
+ end
23
+
24
+ def after_response
25
+ return unless response.success?
26
+ request = response.request
27
+ return unless cache?(request)
28
+ options = options(request.options)
29
+ cache_for(options).write(
30
+ key(request, options[:key]),
31
+ to_cache(response),
32
+ cache_options(options)
33
+ )
34
+ end
35
+
36
+ private
37
+
38
+ # return the cache for the given options
39
+ def cache_for(options)
40
+ options.fetch(:use, cache)
41
+ end
42
+
43
+ # do we even need to bother with this interceptor?
44
+ # based on the options, this method will
45
+ # return false if this interceptor cannot work
46
+ def cache?(request)
47
+ return false unless request.options[:cache]
48
+ options = options(request.options)
49
+ cache_for(options) &&
50
+ cached_method?(request.method, options[:methods])
51
+ end
52
+
53
+ # returns the request_options
54
+ # will map deprecated options to the new format
55
+ def options(request_options)
56
+ options = (request_options[:cache] == true) ? {} : request_options[:cache].dup
57
+ map_deprecated_options!(request_options, options)
58
+ options
59
+ end
60
+
61
+ # maps `cache_key` -> `key`, `cache_expires_in` -> `expires_in` and so on
62
+ def map_deprecated_options!(request_options, options)
63
+ deprecated_keys(request_options).each do |deprecated_key|
64
+ new_key = deprecated_key.to_s.gsub(/^cache_/, '').to_sym
65
+ options[new_key] = request_options[deprecated_key]
66
+ end
67
+ end
68
+
69
+ # converts json we read from the cache to an LHC::Response object
70
+ def from_cache(request, data)
71
+ raw = Typhoeus::Response.new(data)
72
+ response = LHC::Response.new(raw, request, from_cache: true)
73
+ request.response = response
74
+ response
75
+ end
76
+
77
+ # converts a LHC::Response object to json, we store in the cache
78
+ def to_cache(response)
79
+ data = {}
80
+ data[:body] = response.body
81
+ data[:code] = response.code
82
+ # convert into a actual hash because the typhoeus headers object breaks marshaling
83
+ data[:headers] = response.headers ? Hash[response.headers] : response.headers
84
+ # return_code is quite important as Typhoeus relies on it in order to determin 'success?'
85
+ data[:return_code] = response.options[:return_code]
86
+ # in a test scenario typhoeus uses mocks and not return_code to determine 'success?'
87
+ data[:mock] = response.mock
88
+ data
89
+ end
90
+
91
+ def key(request, key)
92
+ unless key
93
+ key = "#{request.method.upcase} #{request.url}"
94
+ key += "?#{request.params.to_query}" unless request.params.blank?
95
+ end
96
+ "LHC_CACHE(v#{CACHE_VERSION}): #{key}"
97
+ end
98
+
99
+ # Checks if the provided method should be cached
100
+ # in regards of the provided options.
101
+ def cached_method?(method, cached_methods)
102
+ (cached_methods || [:get]).include?(method)
103
+ end
104
+
105
+ # extracts the options that should be forwarded to
106
+ # the cache
107
+ def cache_options(input = {})
108
+ input.each_with_object({}) do |(key, value), result|
109
+ result[key] = value if key.in? FORWARDED_OPTIONS
110
+ result
111
+ end
112
+ end
113
+
114
+ # grabs the deprecated keys from the request options
115
+ def deprecated_keys(request_options)
116
+ request_options.keys.select { |k| k =~ /^cache_.*/ }.sort
117
+ end
118
+
119
+ # emits a deprecation warning if necessary
120
+ def deprecation_warning(request_options)
121
+ unless deprecated_keys(request_options).empty?
122
+ ActiveSupport::Deprecation.warn(
123
+ "Cache options have changed! #{deprecated_keys(request_options).join(', ')} are deprecated and will be removed in future versions."
124
+ )
125
+ end
126
+ end
127
+ end