lhc 12.0.0

Sign up to get free protection for your applications and to get access to all the features.
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