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
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'DHC'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.rdoc')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ begin
20
+ require 'rspec/core/rake_task'
21
+ RSpec::Core::RakeTask.new(:spec)
22
+ task default: :spec
23
+ end
24
+
25
+ Bundler::GemHelper.install_tasks
data/dhc.gemspec ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+
5
+ # Maintain your gem's version:
6
+ require 'dhc/version'
7
+
8
+ # Describe your gem and declare its dependencies:
9
+ Gem::Specification.new do |s|
10
+ s.name = 'dhc'
11
+ s.version = DHC::VERSION
12
+ s.authors = ['https://github.com/DePayFi/dhc/contributors']
13
+ s.email = ['engineering@depay.fi']
14
+ s.homepage = 'https://github.com/DePayFi/dhc'
15
+ s.summary = 'Advanced HTTP Client for Ruby, fueled with interceptors'
16
+ s.description = 'DHC is an advanced HTTP client. Implementing basic http-communication enhancements like interceptors, exception handling, format handling, accessing response data, configuring endpoints and placeholders and fully compatible, RFC-compliant URL-template support.'
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- spec/*`.split("\n")
20
+ s.require_paths = ['lib']
21
+
22
+ s.requirements << 'Ruby >= 2.0.0'
23
+ s.required_ruby_version = '>= 2.7.0'
24
+
25
+ s.add_dependency 'activesupport', '>= 5.2'
26
+ s.add_dependency 'addressable'
27
+ s.add_dependency 'typhoeus', '>= 0.11'
28
+
29
+ s.add_development_dependency 'prometheus-client', '~> 0.7.1'
30
+ s.add_development_dependency 'pry'
31
+ s.add_development_dependency 'rails', '>= 5.2'
32
+ s.add_development_dependency 'redis'
33
+ s.add_development_dependency 'rspec-rails', '>= 3.0.0'
34
+ s.add_development_dependency 'rubocop'
35
+ s.add_development_dependency 'rubocop-rspec'
36
+ s.add_development_dependency 'timecop'
37
+ s.add_development_dependency 'webmock'
38
+
39
+ s.license = 'GPL-3.0'
40
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This was copied from: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/deep_transform_values.rb
4
+ class Hash
5
+ # Returns a new hash with all values converted by the block operation.
6
+ # This includes the values from the root hash and from all
7
+ # nested hashes and arrays.
8
+ #
9
+ # hash = { person: { name: 'Rob', age: '28' } }
10
+ #
11
+ # hash.deep_transform_values{ |value| value.to_s.upcase }
12
+ # # => {person: {name: "ROB", age: "28"}}
13
+ def deep_transform_values(&block)
14
+ _deep_transform_values_in_object(self, &block)
15
+ end
16
+
17
+ # Destructively converts all values by using the block operation.
18
+ # This includes the values from the root hash and from all
19
+ # nested hashes and arrays.
20
+ def deep_transform_values!(&block)
21
+ _deep_transform_values_in_object!(self, &block)
22
+ end
23
+
24
+ private
25
+
26
+ # support methods for deep transforming nested hashes and arrays
27
+ def _deep_transform_values_in_object(object, &block)
28
+ case object
29
+ when Hash
30
+ object.transform_values { |value| _deep_transform_values_in_object(value, &block) }
31
+ when Array
32
+ object.map { |e| _deep_transform_values_in_object(e, &block) }
33
+ else
34
+ yield(object)
35
+ end
36
+ end
37
+
38
+ def _deep_transform_values_in_object!(object, &block)
39
+ case object
40
+ when Hash
41
+ object.transform_values! { |value| _deep_transform_values_in_object!(value, &block) }
42
+ when Array
43
+ object.map! { |e| _deep_transform_values_in_object!(e, &block) }
44
+ else
45
+ yield(object)
46
+ end
47
+ end
48
+ end
data/lib/dhc.rb ADDED
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'typhoeus'
4
+ require 'active_support/core_ext/object/blank'
5
+ require 'active_support/core_ext/hash/keys'
6
+
7
+ module DHC
8
+ autoload :BasicMethodsConcern, 'dhc/concerns/dhc/basic_methods_concern'
9
+ autoload :ConfigurationConcern, 'dhc/concerns/dhc/configuration_concern'
10
+ autoload :FixInvalidEncodingConcern, 'dhc/concerns/dhc/fix_invalid_encoding_concern'
11
+ autoload :FormatsConcern, 'dhc/concerns/dhc/formats_concern'
12
+
13
+ include BasicMethodsConcern
14
+ include ConfigurationConcern
15
+ include FormatsConcern
16
+
17
+ autoload :Auth, 'dhc/interceptors/auth'
18
+ autoload :Caching, 'dhc/interceptors/caching'
19
+ autoload :DefaultTimeout, 'dhc/interceptors/default_timeout'
20
+ autoload :Logging, 'dhc/interceptors/logging'
21
+ autoload :Prometheus, 'dhc/interceptors/prometheus'
22
+ autoload :Retry, 'dhc/interceptors/retry'
23
+ autoload :Throttle, 'dhc/interceptors/throttle'
24
+
25
+ autoload :Config, 'dhc/config'
26
+ autoload :Endpoint, 'dhc/endpoint'
27
+
28
+ autoload :Error, 'dhc/error'
29
+ autoload :ClientError, 'dhc/errors/client_error'
30
+ autoload :BadRequest, 'dhc/errors/client_error'
31
+ autoload :Unauthorized, 'dhc/errors/client_error'
32
+ autoload :PaymentRequired, 'dhc/errors/client_error'
33
+ autoload :Forbidden, 'dhc/errors/client_error'
34
+ autoload :Forbidden, 'dhc/errors/client_error'
35
+ autoload :NotFound, 'dhc/errors/client_error'
36
+ autoload :MethodNotAllowed, 'dhc/errors/client_error'
37
+ autoload :NotAcceptable, 'dhc/errors/client_error'
38
+ autoload :ProxyAuthenticationRequired, 'dhc/errors/client_error'
39
+ autoload :RequestTimeout, 'dhc/errors/client_error'
40
+ autoload :Conflict, 'dhc/errors/client_error'
41
+ autoload :Gone, 'dhc/errors/client_error'
42
+ autoload :LengthRequired, 'dhc/errors/client_error'
43
+ autoload :PreconditionFailed, 'dhc/errors/client_error'
44
+ autoload :RequestEntityTooLarge, 'dhc/errors/client_error'
45
+ autoload :RequestUriToLong, 'dhc/errors/client_error'
46
+ autoload :UnsupportedMediaType, 'dhc/errors/client_error'
47
+ autoload :RequestedRangeNotSatisfiable, 'dhc/errors/client_error'
48
+ autoload :ExpectationFailed, 'dhc/errors/client_error'
49
+ autoload :UnprocessableEntity, 'dhc/errors/client_error'
50
+ autoload :Locked, 'dhc/errors/client_error'
51
+ autoload :FailedDependency, 'dhc/errors/client_error'
52
+ autoload :UpgradeRequired, 'dhc/errors/client_error'
53
+ autoload :ParserError, 'dhc/errors/parser_error'
54
+ autoload :ServerError, 'dhc/errors/server_error'
55
+ autoload :InternalServerError, 'dhc/errors/server_error'
56
+ autoload :NotImplemented, 'dhc/errors/server_error'
57
+ autoload :BadGateway, 'dhc/errors/server_error'
58
+ autoload :ServiceUnavailable, 'dhc/errors/server_error'
59
+ autoload :GatewayTimeout, 'dhc/errors/server_error'
60
+ autoload :HttpVersionNotSupported, 'dhc/errors/server_error'
61
+ autoload :InsufficientStorage, 'dhc/errors/server_error'
62
+ autoload :NotExtended, 'dhc/errors/server_error'
63
+ autoload :Timeout, 'dhc/errors/timeout'
64
+ autoload :UnknownError, 'dhc/errors/unknown_error'
65
+
66
+ autoload :Interceptor, 'dhc/interceptor'
67
+ autoload :Interceptors, 'dhc/interceptors'
68
+ autoload :Formats, 'dhc/formats'
69
+ autoload :Format, 'dhc/format'
70
+ autoload :Monitoring, 'dhc/interceptors/monitoring'
71
+ autoload :Request, 'dhc/request'
72
+ autoload :Response, 'dhc/response'
73
+ autoload :Rollbar, 'dhc/interceptors/rollbar'
74
+ autoload :Zipkin, 'dhc/interceptors/zipkin'
75
+
76
+ require 'dhc/railtie' if defined?(Rails)
77
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ module DHC
6
+ module BasicMethodsConcern
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def request(options)
11
+ if options.is_a? Array
12
+ parallel_requests(options)
13
+ else
14
+ DHC::Request.new(options).response
15
+ end
16
+ end
17
+
18
+ %i[get post put delete].each do |http_method|
19
+ define_method(http_method) do |url, options = {}|
20
+ request(options.merge(
21
+ url: url,
22
+ method: http_method
23
+ ))
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def parallel_requests(options)
30
+ hydra = Typhoeus::Hydra.new # do not use memoization !
31
+ requests = []
32
+ options.each do |option|
33
+ request = DHC::Request.new(option, false)
34
+ requests << request
35
+ hydra.queue request.raw unless request.response.present?
36
+ end
37
+ hydra.run
38
+ requests.map(&:response)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ module DHC
6
+ module ConfigurationConcern
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def config
11
+ DHC::Config.instance
12
+ end
13
+
14
+ def configure
15
+ DHC::Config.instance.reset
16
+ yield config
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ module DHC
6
+ module FixInvalidEncodingConcern
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ # fix strings that contain non-UTF8 encoding in a forceful way
11
+ # should none of the fix-attempts be successful,
12
+ # an empty string is returned instead
13
+ def fix_invalid_encoding(string)
14
+ return string unless string.is_a?(String)
15
+ result = string.dup
16
+
17
+ # we assume it's ISO-8859-1 first
18
+ if !result.valid_encoding? || !utf8?(result)
19
+ result.encode!('UTF-8', 'ISO-8859-1', invalid: :replace, undef: :replace, replace: '')
20
+ end
21
+
22
+ # if it's still an issue, try with BINARY
23
+ if !result.valid_encoding? || !utf8?(result)
24
+ result.encode!('UTF-8', 'BINARY', invalid: :replace, undef: :replace, replace: '')
25
+ end
26
+
27
+ # if its STILL an issue, return an empty string :(
28
+ if !result.valid_encoding? || !utf8?(result)
29
+ result = ''
30
+ end
31
+
32
+ result
33
+ end
34
+
35
+ private
36
+
37
+ def utf8?(string)
38
+ string.encoding == Encoding::UTF_8
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DHC
4
+ module FormatsConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def form
9
+ DHC::Formats::Form
10
+ end
11
+
12
+ def json
13
+ DHC::Formats::JSON
14
+ end
15
+
16
+ def multipart
17
+ DHC::Formats::Multipart
18
+ end
19
+
20
+ def plain
21
+ DHC::Formats::Plain
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'dhc/version'
5
+
6
+ module DHC
7
+ class Request
8
+ module UserAgentConcern
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ Typhoeus::Config.user_agent = begin
13
+ version = DHC::VERSION
14
+ application = nil
15
+ if defined?(Rails)
16
+ app_class = Rails.application.class
17
+ application = (ActiveSupport.gem_version >= Gem::Version.new('6.0.0')) ? app_class.module_parent_name : app_class.parent_name
18
+ end
19
+
20
+ "DHC (#{[version, application].compact.join('; ')}) [https://github.com/DePayFi/dhc]"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
data/lib/dhc/config.rb ADDED
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+
5
+ class DHC::Config
6
+ include Singleton
7
+
8
+ def initialize
9
+ @endpoints = {}
10
+ @placeholders = {}
11
+ end
12
+
13
+ def endpoint(name, url, options = {})
14
+ name = name.to_sym
15
+ raise 'Endpoint already exists for that name' if @endpoints[name]
16
+ @endpoints[name] = DHC::Endpoint.new(url, options)
17
+ end
18
+
19
+ def endpoints
20
+ @endpoints.dup
21
+ end
22
+
23
+ def placeholder(name, value)
24
+ name = name.to_sym
25
+ raise 'Placeholder already exists for that name' if @placeholders[name]
26
+ @placeholders[name] = value
27
+ end
28
+
29
+ def placeholders
30
+ @placeholders.dup
31
+ end
32
+
33
+ def interceptors
34
+ (@interceptors || []).dup
35
+ end
36
+
37
+ def interceptors=(interceptors)
38
+ raise 'Default interceptors already set and can only be set once' if @interceptors
39
+ @interceptors = interceptors
40
+ end
41
+
42
+ def reset
43
+ @endpoints = {}
44
+ @placeholders = {}
45
+ @interceptors = nil
46
+ end
47
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'addressable/template'
4
+
5
+ # An endpoint is an url that leads to a backend resource.
6
+ # The url can also be an url-template (https://tools.ietf.org/html/rfc6570).
7
+ class DHC::Endpoint
8
+ attr_accessor :url
9
+ attr_writer :options
10
+
11
+ def initialize(url, options = nil)
12
+ self.url = url
13
+ self.options = options
14
+ end
15
+
16
+ def uri
17
+ @uri ||= Addressable::Template.new(url)
18
+ end
19
+
20
+ def compile(params)
21
+ context = DHC.config.placeholders.deep_dup
22
+ context.merge!(params) if params.is_a?(Hash)
23
+ expanded = uri.partial_expand(context)
24
+
25
+ if expanded.variables.empty?
26
+ expanded.pattern
27
+ else
28
+ raise("Compilation incomplete. Unable to find value for #{expanded.variables.join(', ')}.")
29
+ end
30
+ end
31
+
32
+ # Endpoint options are immutable
33
+ def options
34
+ @options.deep_dup
35
+ end
36
+
37
+ # Removes keys from provided params hash
38
+ # when they are used for interpolation.
39
+ def remove_interpolated_params!(params)
40
+ params ||= {}
41
+ removed = params.slice(*placeholders)
42
+ params.except!(*placeholders)
43
+
44
+ removed
45
+ end
46
+
47
+ # Returns all placeholders found in the url-template.
48
+ # They are alphabetically sorted.
49
+ def placeholders
50
+ uri.variables.sort.map(&:to_sym)
51
+ end
52
+
53
+ # Compares a concrete url with a template
54
+ # Returns true if concrete url is covered by the template
55
+ # Example: {+datastore}/contracts/{id} == http://depay.fi/contracts/1
56
+ def match?(url)
57
+ return true if url == uri.pattern
58
+ match_data = match_data(url)
59
+ return false if match_data.nil?
60
+
61
+ first = match_data.values.first
62
+ last = match_data.values.last
63
+ url_format = File.extname(url)
64
+
65
+ # eliminate false positives in form:
66
+ # http://host/feedbacks matches {+service}/{entry_id}/feedbacks (service: http:/, entry_id: host)
67
+ first&.match(%(https?:/$)).nil? &&
68
+ # eliminates false positives in form:
69
+ # http://host/entries/123.json matches {+service}/entries/{id} (service: http://host, id: 123.json)
70
+ (url_format.blank? || !last&.ends_with?(url_format))
71
+ end
72
+
73
+ # Extracts the values from url and
74
+ # creates params according to template
75
+ def values_as_params(url)
76
+ match_data = match_data(url)
77
+ return if match_data.nil?
78
+ Hash[match_data.variables.map(&:to_sym).zip(match_data.values)]
79
+ end
80
+
81
+ # Checks if the name has a match in the current context
82
+ def find_value(name, mapping)
83
+ context = DHC.config.placeholders.deep_dup
84
+ context.merge!(mapping)
85
+
86
+ context[name]
87
+ end
88
+
89
+ # Compares a concrete url with a template
90
+ # Returns true if concrete url is covered by the template
91
+ # Example: {+datastore}/contracts/{id} == http://depay.fi/contracts/1
92
+ def self.match?(url, template)
93
+ new(template).match?(url)
94
+ end
95
+
96
+ # Returns all placeholders found in the url-template.
97
+ # They are alphabetically sorted.
98
+ def self.placeholders(template)
99
+ new(template).placeholders
100
+ end
101
+
102
+ # Extracts the values from url and
103
+ # creates params according to template
104
+ def self.values_as_params(template, url)
105
+ raise("#{url} does not match the template: #{template}") unless match?(url, template)
106
+ new(template).values_as_params(url)
107
+ end
108
+
109
+ private
110
+
111
+ def match_data(url)
112
+ parsed = URI.parse(url)
113
+ parsed.query = parsed.fragment = nil
114
+
115
+ uri.match(parsed)
116
+ rescue URI::InvalidURIError
117
+ nil
118
+ end
119
+ end