restify 2.1.0 → 3.0.0.rc3

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 (222) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -0
  3. data/README.md +30 -5
  4. data/lib/restify/adapter/base.rb +10 -1
  5. data/lib/restify/adapter/ethon/easy.rb +147 -0
  6. data/lib/restify/adapter/ethon/event_loop.rb +255 -0
  7. data/lib/restify/adapter/ethon/options.rb +81 -0
  8. data/lib/restify/adapter/ethon/pool.rb +43 -0
  9. data/lib/restify/adapter/ethon.rb +211 -0
  10. data/lib/restify/adapter/loop_lock.rb +124 -0
  11. data/lib/restify/adapter/telemetry.rb +7 -5
  12. data/lib/restify/adapter/typhoeus.rb +52 -23
  13. data/lib/restify/context.rb +16 -6
  14. data/lib/restify/error.rb +13 -0
  15. data/lib/restify/global.rb +9 -30
  16. data/lib/restify/logging.rb +10 -4
  17. data/lib/restify/processors/base/parsing.rb +43 -26
  18. data/lib/restify/promise.rb +194 -42
  19. data/lib/restify/registry.rb +2 -2
  20. data/lib/restify/relation.rb +27 -13
  21. data/lib/restify/request.rb +2 -2
  22. data/lib/restify/resource.rb +20 -2
  23. data/lib/restify/response.rb +39 -3
  24. data/lib/restify/timeout.rb +23 -34
  25. data/lib/restify/version.rb +3 -3
  26. data/lib/restify/webmock.rb +126 -0
  27. data/lib/restify.rb +5 -2
  28. data/spec/restify/adapter/ethon/easy_spec.rb +111 -0
  29. data/spec/restify/adapter/ethon/event_loop_spec.rb +96 -0
  30. data/spec/restify/adapter/ethon/options_spec.rb +104 -0
  31. data/spec/restify/adapter/ethon/pool_spec.rb +81 -0
  32. data/spec/restify/adapter/ethon_spec.rb +307 -0
  33. data/spec/restify/adapter/loop_lock_spec.rb +144 -0
  34. data/spec/restify/adapter/telemetry_spec.rb +61 -0
  35. data/spec/restify/adapter/typhoeus_spec.rb +26 -0
  36. data/spec/restify/cache_spec.rb +1 -3
  37. data/spec/restify/context_spec.rb +69 -0
  38. data/spec/restify/features/opentelemetry_spec.rb +2 -0
  39. data/spec/restify/features/protocols_spec.rb +28 -0
  40. data/spec/restify/features/redirects_spec.rb +45 -0
  41. data/spec/restify/features/request_errors_spec.rb +1 -1
  42. data/spec/restify/features/response_bodies_spec.rb +45 -0
  43. data/spec/restify/features/response_encoding_spec.rb +82 -0
  44. data/spec/restify/features/response_errors_spec.rb +22 -1
  45. data/spec/restify/features/webmock_spec.rb +92 -1
  46. data/spec/restify/global_spec.rb +78 -10
  47. data/spec/restify/processors/json_spec.rb +103 -0
  48. data/spec/restify/processors/msgpack_spec.rb +36 -0
  49. data/spec/restify/promise_spec.rb +288 -9
  50. data/spec/restify/relation_spec.rb +25 -6
  51. data/spec/restify/request_spec.rb +26 -0
  52. data/spec/restify/timeout_spec.rb +43 -33
  53. data/spec/spec_helper.rb +10 -6
  54. data/spec/support/stub_server.rb +4 -4
  55. metadata +32 -221
  56. data/vendor/bundle/ruby/4.0.0/bundler/gems/my-rubocop-2b861962124a/LICENSE +0 -21
  57. data/vendor/bundle/ruby/4.0.0/bundler/gems/my-rubocop-2b861962124a/README.md +0 -23
  58. data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/CHANGELOG.md +0 -561
  59. data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/MIT-LICENSE +0 -20
  60. data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/README.rdoc +0 -40
  61. data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/CHANGELOG.md +0 -326
  62. data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/LICENSE.txt +0 -202
  63. data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/README.md +0 -121
  64. data/vendor/bundle/ruby/4.0.0/gems/ast-2.4.3/LICENSE.MIT +0 -20
  65. data/vendor/bundle/ruby/4.0.0/gems/ast-2.4.3/README.YARD.md +0 -12
  66. data/vendor/bundle/ruby/4.0.0/gems/base64-0.3.0/README.md +0 -48
  67. data/vendor/bundle/ruby/4.0.0/gems/bigdecimal-4.1.2/LICENSE +0 -56
  68. data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/CHANGELOG.md +0 -615
  69. data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/LICENSE.txt +0 -21
  70. data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/README.md +0 -409
  71. data/vendor/bundle/ruby/4.0.0/gems/connection_pool-3.0.2/LICENSE +0 -20
  72. data/vendor/bundle/ruby/4.0.0/gems/connection_pool-3.0.2/README.md +0 -182
  73. data/vendor/bundle/ruby/4.0.0/gems/crack-1.0.1/LICENSE +0 -20
  74. data/vendor/bundle/ruby/4.0.0/gems/crack-1.0.1/README.md +0 -43
  75. data/vendor/bundle/ruby/4.0.0/gems/diff-lcs-1.6.2/CHANGELOG.md +0 -518
  76. data/vendor/bundle/ruby/4.0.0/gems/diff-lcs-1.6.2/README.md +0 -92
  77. data/vendor/bundle/ruby/4.0.0/gems/docile-1.4.1/LICENSE +0 -21
  78. data/vendor/bundle/ruby/4.0.0/gems/docile-1.4.1/README.md +0 -409
  79. data/vendor/bundle/ruby/4.0.0/gems/drb-2.2.3/LICENSE.txt +0 -22
  80. data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/CHANGELOG.md +0 -417
  81. data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/LICENSE +0 -20
  82. data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/README.md +0 -118
  83. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/CHANGELOG.md +0 -507
  84. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/LICENSE +0 -24
  85. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/LICENSE.SPECS +0 -22
  86. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/README.md +0 -138
  87. data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/LICENSE +0 -19
  88. data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/README.md +0 -323
  89. data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/changelog.md +0 -127
  90. data/vendor/bundle/ruby/4.0.0/gems/hitimes-3.2.0/LICENSE.txt +0 -16
  91. data/vendor/bundle/ruby/4.0.0/gems/hitimes-3.2.0/README.md +0 -187
  92. data/vendor/bundle/ruby/4.0.0/gems/i18n-1.15.2/MIT-LICENSE +0 -20
  93. data/vendor/bundle/ruby/4.0.0/gems/i18n-1.15.2/README.md +0 -139
  94. data/vendor/bundle/ruby/4.0.0/gems/json-2.19.9/README.md +0 -310
  95. data/vendor/bundle/ruby/4.0.0/gems/language_server-protocol-3.17.0.5/LICENSE.txt +0 -21
  96. data/vendor/bundle/ruby/4.0.0/gems/language_server-protocol-3.17.0.5/README.md +0 -88
  97. data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/CHANGELOG.md +0 -15
  98. data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/LICENSE.txt +0 -21
  99. data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/README.md +0 -173
  100. data/vendor/bundle/ruby/4.0.0/gems/little-plugger-1.1.4/README.rdoc +0 -53
  101. data/vendor/bundle/ruby/4.0.0/gems/logger-1.7.0/README.md +0 -104
  102. data/vendor/bundle/ruby/4.0.0/gems/logging-2.4.0/LICENSE +0 -22
  103. data/vendor/bundle/ruby/4.0.0/gems/logging-2.4.0/README.md +0 -140
  104. data/vendor/bundle/ruby/4.0.0/gems/minitest-6.0.6/README.rdoc +0 -763
  105. data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/ChangeLog +0 -383
  106. data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/LICENSE +0 -177
  107. data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/README.md +0 -302
  108. data/vendor/bundle/ruby/4.0.0/gems/multi_json-1.21.1/LICENSE.md +0 -20
  109. data/vendor/bundle/ruby/4.0.0/gems/multi_json-1.21.1/README.md +0 -281
  110. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/ext/libev/LICENSE +0 -37
  111. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/ext/libev/README +0 -59
  112. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/license.md +0 -80
  113. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/readme.md +0 -91
  114. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/CHANGELOG.md +0 -219
  115. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/LICENSE +0 -201
  116. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/README.md +0 -68
  117. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/CHANGELOG.md +0 -110
  118. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/LICENSE +0 -201
  119. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/README.md +0 -62
  120. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/CHANGELOG.md +0 -97
  121. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/LICENSE +0 -201
  122. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/README.md +0 -155
  123. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/CHANGELOG.md +0 -192
  124. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/LICENSE +0 -201
  125. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/README.md +0 -66
  126. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/CHANGELOG.md +0 -31
  127. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/LICENSE +0 -201
  128. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/README.md +0 -40
  129. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/CHANGELOG.md +0 -302
  130. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/LICENSE +0 -201
  131. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/README.md +0 -101
  132. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/CHANGELOG.md +0 -65
  133. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/LICENSE +0 -201
  134. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/README.md +0 -115
  135. data/vendor/bundle/ruby/4.0.0/gems/parallel-2.1.0/MIT-LICENSE.txt +0 -20
  136. data/vendor/bundle/ruby/4.0.0/gems/parser-3.3.11.1/LICENSE.txt +0 -26
  137. data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/CHANGELOG.md +0 -786
  138. data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/LICENSE.md +0 -7
  139. data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/README.md +0 -143
  140. data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/CHANGELOG.md +0 -649
  141. data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/LICENSE.txt +0 -22
  142. data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/README.md +0 -231
  143. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/LICENSE +0 -29
  144. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/README.md +0 -484
  145. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/docs/jungle/README.md +0 -9
  146. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/docs/jungle/rc.d/README.md +0 -74
  147. data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/ChangeLog +0 -846
  148. data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/README.ja.rdoc +0 -58
  149. data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/README.rdoc +0 -60
  150. data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/CHANGELOG.md +0 -1346
  151. data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/MIT-LICENSE +0 -20
  152. data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/README.md +0 -384
  153. data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/Changelog.md +0 -101
  154. data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/LICENSE +0 -20
  155. data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/README.markdown +0 -227
  156. data/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/MIT-LICENSE +0 -21
  157. data/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/README.rdoc +0 -155
  158. data/vendor/bundle/ruby/4.0.0/gems/rake-release-1.4.0/LICENSE +0 -21
  159. data/vendor/bundle/ruby/4.0.0/gems/rake-release-1.4.0/README.md +0 -107
  160. data/vendor/bundle/ruby/4.0.0/gems/regexp_parser-2.12.0/LICENSE +0 -22
  161. data/vendor/bundle/ruby/4.0.0/gems/rexml-3.4.4/LICENSE.txt +0 -22
  162. data/vendor/bundle/ruby/4.0.0/gems/rexml-3.4.4/README.md +0 -57
  163. data/vendor/bundle/ruby/4.0.0/gems/rspec-3.13.2/LICENSE.md +0 -27
  164. data/vendor/bundle/ruby/4.0.0/gems/rspec-3.13.2/README.md +0 -47
  165. data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/Changelog.md +0 -2447
  166. data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/LICENSE.md +0 -26
  167. data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/README.md +0 -389
  168. data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/Changelog.md +0 -1366
  169. data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/LICENSE.md +0 -25
  170. data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/README.md +0 -326
  171. data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/Changelog.md +0 -1351
  172. data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/LICENSE.md +0 -25
  173. data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/README.md +0 -465
  174. data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/Changelog.md +0 -444
  175. data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/LICENSE.md +0 -23
  176. data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/README.md +0 -40
  177. data/vendor/bundle/ruby/4.0.0/gems/rubocop-1.88.0/LICENSE.txt +0 -20
  178. data/vendor/bundle/ruby/4.0.0/gems/rubocop-1.88.0/README.md +0 -255
  179. data/vendor/bundle/ruby/4.0.0/gems/rubocop-ast-1.49.1/LICENSE.txt +0 -20
  180. data/vendor/bundle/ruby/4.0.0/gems/rubocop-ast-1.49.1/README.md +0 -54
  181. data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/CHANGELOG.md +0 -116
  182. data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/MIT-LICENSE.md +0 -21
  183. data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/README.md +0 -91
  184. data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/CHANGELOG.md +0 -135
  185. data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/MIT-LICENSE.md +0 -21
  186. data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/README.md +0 -91
  187. data/vendor/bundle/ruby/4.0.0/gems/rubocop-performance-1.26.1/LICENSE.txt +0 -20
  188. data/vendor/bundle/ruby/4.0.0/gems/rubocop-performance-1.26.1/README.md +0 -100
  189. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/CHANGELOG.md +0 -1136
  190. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/MIT-LICENSE.md +0 -21
  191. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/README.md +0 -112
  192. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/CHANGELOG.md +0 -98
  193. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/MIT-LICENSE.md +0 -21
  194. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/README.md +0 -93
  195. data/vendor/bundle/ruby/4.0.0/gems/ruby-progressbar-1.13.0/LICENSE.txt +0 -19
  196. data/vendor/bundle/ruby/4.0.0/gems/ruby-progressbar-1.13.0/README.md +0 -131
  197. data/vendor/bundle/ruby/4.0.0/gems/securerandom-0.4.1/README.md +0 -72
  198. data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/CHANGELOG.md +0 -191
  199. data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/LICENSE +0 -20
  200. data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/README.md +0 -974
  201. data/vendor/bundle/ruby/4.0.0/gems/simplecov-cobertura-3.2.0/LICENSE +0 -202
  202. data/vendor/bundle/ruby/4.0.0/gems/simplecov-cobertura-3.2.0/README.md +0 -65
  203. data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/CHANGELOG.md +0 -114
  204. data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/LICENSE +0 -20
  205. data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/README.md +0 -30
  206. data/vendor/bundle/ruby/4.0.0/gems/simplecov_json_formatter-0.1.4/CHANGELOG.md +0 -13
  207. data/vendor/bundle/ruby/4.0.0/gems/simplecov_json_formatter-0.1.4/README.md +0 -29
  208. data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/CHANGELOG.md +0 -465
  209. data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/LICENSE +0 -22
  210. data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/README.md +0 -588
  211. data/vendor/bundle/ruby/4.0.0/gems/tzinfo-2.0.6/LICENSE +0 -19
  212. data/vendor/bundle/ruby/4.0.0/gems/tzinfo-2.0.6/README.md +0 -406
  213. data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/CHANGELOG.md +0 -299
  214. data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/MIT-LICENSE.txt +0 -22
  215. data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/README.md +0 -194
  216. data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/CHANGELOG.md +0 -202
  217. data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/MIT-LICENSE.txt +0 -20
  218. data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/README.md +0 -205
  219. data/vendor/bundle/ruby/4.0.0/gems/uri-1.1.1/README.md +0 -55
  220. data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/CHANGELOG.md +0 -2148
  221. data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/LICENSE +0 -20
  222. data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/README.md +0 -1229
@@ -1,15 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logging'
4
-
5
3
  module Restify
6
4
  module Logging
7
5
  def logger
8
- @logger ||= ::Logging.logger[self]
6
+ Restify.logger
9
7
  end
10
8
 
11
9
  def debug(message = nil, tag: nil, **kwargs)
12
- logger.debug do
10
+ logger&.debug(_log_name) do
13
11
  [
14
12
  _log_prefix,
15
13
  *Array(tag),
@@ -19,6 +17,14 @@ module Restify
19
17
  end
20
18
  end
21
19
 
20
+ def error(exception)
21
+ logger&.error(_log_name) { exception }
22
+ end
23
+
24
+ def _log_name
25
+ is_a?(Module) ? name : self.class.name
26
+ end
27
+
22
28
  def _log_prefix
23
29
  nil
24
30
  end
@@ -9,47 +9,64 @@ module Restify
9
9
  # Parses generic data structures into resources
10
10
  #
11
11
  module Parsing
12
+ RELATION_NAME = /\A\w+_url\z/i
13
+
12
14
  def load
13
- parse deserialized_body, root: true
15
+ # No data, e.g. when a server sets a content type but sends an
16
+ # empty body.
17
+ return if body.nil? || body.empty?
18
+
19
+ begin
20
+ data = deserialized_body
21
+ rescue StandardError => e
22
+ # Keep the resource, e.g. with relations from headers, but
23
+ # raise on accessing its data.
24
+ return Resource.new(context, response:, error: e)
25
+ end
26
+
27
+ parse(data, root: true)
14
28
  end
15
29
 
16
30
  def parse(object, root: false)
17
31
  case object
18
- when Hash
19
- data = object.each_with_object({}) {|each, obj| parse_data(each, obj) }
20
- relations = object.each_with_object({}) {|each, obj| parse_rels(each, obj) }
21
-
22
- Resource.new context,
23
- data:,
24
- response: root ? response : nil,
25
- relations:
26
-
27
- when Array
28
- object.map {|each| parse(each) }
29
- else
30
- object
32
+ when Hash then build_resource(object, root)
33
+ when Array then object.map {|each| parse(each) }
34
+ else object
31
35
  end
32
36
  end
33
37
 
34
38
  private
35
39
 
36
- def parse_data(pair, data)
37
- data[pair[0].to_s] = parse pair[1]
40
+ # Build data and relations of a resource in one pass.
41
+ def build_resource(object, root)
42
+ data = {}
43
+ relations = {}
44
+
45
+ object.each_pair do |key, value|
46
+ key = key.to_s
47
+ data[key] = parse(value)
48
+ parse_relation(relations, key, value) if value.is_a?(String)
49
+ end
50
+
51
+ Resource.new(
52
+ context,
53
+ data:,
54
+ response: root ? response : nil,
55
+ relations:,
56
+ )
38
57
  end
39
58
 
40
- def parse_rels(pair, relations)
41
- name = case pair[0].to_s.downcase
42
- when /\A(\w+)_url\z/
43
- Regexp.last_match[1]
44
- when 'url'
45
- 'self'
46
- else
47
- return
59
+ def parse_relation(relations, key, value)
60
+ name = if key.match?(RELATION_NAME)
61
+ key[0, key.length - 4].downcase
62
+ elsif key.casecmp?('url')
63
+ 'self'
48
64
  end
49
65
 
50
- return if relations.key?(name) || pair[1].nil? || pair[1].to_s =~ /\A\w*\z/
66
+ return if name.nil? || relations.key?(name)
67
+ return if value.empty?
51
68
 
52
- relations[name] = pair[1].to_s
69
+ relations[name] = value
53
70
  end
54
71
  end
55
72
  end
@@ -1,83 +1,229 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Restify
4
- class Promise < Concurrent::IVar
4
+ class Promise
5
+ class AlreadyCompleteError < StandardError; end
6
+
7
+ # @api private
8
+ attr_writer :driver
9
+
10
+ # @api private
11
+ attr_reader :reason
12
+
5
13
  def initialize(*dependencies, &task)
14
+ @mutex = Mutex.new
15
+ @condition = nil
16
+ @observers = nil
17
+ @driver = nil
18
+ @state = :pending
19
+ @value = nil
20
+ @reason = nil
6
21
  @task = task
7
- @dependencies = dependencies.flatten
22
+ @dependencies = dependencies.empty? ? nil : dependencies.flatten
8
23
 
9
- super(&nil)
24
+ # When dependencies were passed in, but none are left after
25
+ # flattening, then we don't have to wait for explicit dependencies
26
+ # or resolution through a writer.
27
+ fulfill([]) if !@task && @dependencies&.empty?
28
+ end
10
29
 
11
- # When dependencies were passed in, but none are left after flattening,
12
- # then we don't have to wait for explicit dependencies or resolution
13
- # through a writer.
14
- complete true, [], nil if !@task && @dependencies.empty? && dependencies.any?
30
+ def pending?
31
+ @state == :pending
15
32
  end
16
33
 
34
+ def fulfilled?
35
+ @state == :fulfilled
36
+ end
37
+
38
+ def rejected?
39
+ @state == :rejected
40
+ end
41
+
42
+ def complete?
43
+ @state == :fulfilled || @state == :rejected
44
+ end
45
+
46
+ def incomplete?
47
+ !complete?
48
+ end
49
+
50
+ # Wait until the promise is complete.
51
+ #
52
+ # @param timeout [Numeric, Restify::Timeout, nil] Maximum seconds
53
+ # to wait, or `Restify::Timeout.default_timeout` if nil.
54
+ #
55
+ # @raise [Restify::Timeout::Error] When the timeout expired first.
56
+ #
57
+ # @return [self]
58
+ #
17
59
  def wait(timeout = nil)
18
- t = Timeout.new(timeout, self)
60
+ return self if complete?
61
+
62
+ timeout = Timeout.new(timeout, self)
19
63
 
20
- execute(t) if pending?
64
+ # Let the driver run on the current thread instead of sleeping and
65
+ # switching to another thread if possible. The driver returns when
66
+ # the promise is complete, the timeout expired, or it cannot run
67
+ # here.
68
+ @driver&.drive(self, timeout)
21
69
 
22
- super
23
- raise t if incomplete?
70
+ until complete?
71
+ remaining = timeout.remaining
72
+ raise timeout unless remaining.positive?
73
+
74
+ run(timeout) if claim(remaining)
75
+ end
24
76
 
25
77
  self
26
78
  end
27
79
 
80
+ # Wait until the promise is complete, and return its value, or nil
81
+ # if it was rejected.
82
+ #
83
+ # @see #wait
84
+ #
85
+ def value(timeout = nil)
86
+ wait(timeout)
87
+ @value
88
+ end
89
+
90
+ # Wait until the promise is complete, and return its value, or raise
91
+ # the reason it was rejected with.
92
+ #
93
+ # @see #wait
94
+ #
95
+ def value!(timeout = nil)
96
+ wait(timeout)
97
+ raise @reason if rejected?
98
+
99
+ @value
100
+ end
101
+
102
+ # Return a new promise that will be fulfilled with the result of the
103
+ # given block once this promise is complete.
104
+ #
28
105
  def then(&)
29
- Promise.new([self], &)
106
+ Promise.new(self, &)
30
107
  end
31
108
 
32
- def execute(timeout = nil)
33
- synchronize { ns_execute timeout }
109
+ # @api private
110
+ #
111
+ def add_observer(&block)
112
+ complete = @mutex.synchronize do
113
+ (@observers ||= []) << block unless complete?
114
+ complete?
115
+ end
116
+
117
+ yield(@value, @reason) if complete
118
+
119
+ self
34
120
  end
35
121
 
36
- protected
122
+ private
37
123
 
38
- # @!visibility private
39
- def ns_execute(timeout = nil)
40
- return unless compare_and_set_state(:processing, :pending)
41
- return unless @task || @dependencies.any?
124
+ def fulfill(value)
125
+ complete(:fulfilled, value, nil)
126
+ end
42
127
 
43
- executor = Concurrent::SafeTaskExecutor.new \
44
- method(:ns_exec), rescue_exception: true
128
+ def reject(reason)
129
+ complete(:rejected, nil, reason)
130
+ end
131
+
132
+ def complete(state, value, reason)
133
+ observers = @mutex.synchronize do
134
+ raise AlreadyCompleteError.new("Promise already #{@state}") if complete?
135
+
136
+ # Set the state last, as it is read without holding the lock:
137
+ @value = value
138
+ @reason = reason
139
+ @state = state
140
+
141
+ # Release everything not needed anymore:
142
+ @task = @dependencies = nil
143
+
144
+ @condition&.broadcast
145
+
146
+ @observers.tap { @observers = nil }
147
+ end
45
148
 
46
- success, value, reason = executor.execute(timeout)
149
+ observers&.each do |observer|
150
+ observer.call(value, reason)
151
+ rescue StandardError => e
152
+ Restify.logger&.error(self.class.name) { e }
153
+ end
47
154
 
48
- complete success, value, reason
155
+ self
49
156
  end
50
157
 
51
- # @!visibility private
52
- def ns_exec(timeout = nil)
53
- t = Timeout.new(timeout, self)
158
+ # Claim running the task for the current thread, or sleep until the
159
+ # promise is complete, or can be claimed, e.g. because another
160
+ # thread gave up running it.
161
+ #
162
+ # The mutex is only held to claim, not while running the task. Other
163
+ # threads sleep on the condition variable with their own remaining
164
+ # time, whereas blocking on the mutex could not time out.
165
+ #
166
+ def claim(remaining)
167
+ @mutex.synchronize do
168
+ next false if complete?
169
+
170
+ if @state == :pending && (@task || @dependencies)
171
+ @state = :processing
172
+ next true
173
+ end
54
174
 
55
- args = @dependencies.map do |d|
56
- t.wait_on!(d)
175
+ (@condition ||= ConditionVariable.new).wait(@mutex, remaining)
176
+ false
57
177
  end
178
+ end
179
+
180
+ def run(timeout)
181
+ until complete?
182
+ if (rejected = @dependencies&.find {|d| d.wait(timeout).rejected? })
183
+ return reject(rejected.reason)
184
+ end
185
+
186
+ args = @dependencies&.map(&:value)
187
+
188
+ begin
189
+ value = @task ? @task.call(*args) : args
190
+ rescue Exception => e # rubocop:disable Lint/RescueException
191
+ return reject(e)
192
+ end
58
193
 
59
- value = @task ? @task.call(*args) : args
60
- value = t.wait_on!(value) while value.is_a?(Promise)
61
- value
194
+ if value.is_a?(Promise)
195
+ @dependencies = [value]
196
+ @task = ->(v) { v }
197
+ else
198
+ fulfill(value)
199
+ end
200
+ end
201
+ ensure
202
+ # Give up the claim when the timeout expired, or the thread was
203
+ # killed. Another waiting thread can take over, and continue with
204
+ # the dependencies completed so far.
205
+ if @state == :processing
206
+ @mutex.synchronize do
207
+ @state = :pending
208
+ @condition&.broadcast
209
+ end
210
+ end
62
211
  end
63
212
 
64
213
  class << self
65
- def create
66
- promise = Promise.new
214
+ def create(driver: nil)
215
+ promise = new
216
+ promise.driver = driver
67
217
  yield Writer.new(promise)
68
218
  promise
69
219
  end
70
220
 
71
221
  def fulfilled(value)
72
- create do |writer|
73
- writer.fulfill value
74
- end
222
+ new.send(:fulfill, value)
75
223
  end
76
224
 
77
- def rejected(value)
78
- create do |writer|
79
- writer.reject value
80
- end
225
+ def rejected(reason)
226
+ new.send(:reject, reason)
81
227
  end
82
228
  end
83
229
 
@@ -87,11 +233,17 @@ module Restify
87
233
  end
88
234
 
89
235
  def fulfill(value)
90
- @promise.send :complete, true, value, nil
236
+ @promise.send(:fulfill, value)
91
237
  end
92
238
 
93
239
  def reject(reason)
94
- @promise.send :complete, false, nil, reason
240
+ @promise.send(:reject, reason)
241
+ end
242
+
243
+ def set
244
+ fulfill(yield)
245
+ rescue Exception => e # rubocop:disable Lint/RescueException
246
+ reject(e)
95
247
  end
96
248
  end
97
249
  end
@@ -6,8 +6,8 @@ module Restify
6
6
  @registry = {}
7
7
  end
8
8
 
9
- def store(name, uri, **opts)
10
- @registry[name] = Context.new(uri, **opts)
9
+ def store(name, uri, **)
10
+ @registry[name] = Context.new(uri, **)
11
11
  end
12
12
 
13
13
  def fetch(name)
@@ -19,20 +19,20 @@ module Restify
19
19
  @template = Addressable::Template.new(template)
20
20
  end
21
21
 
22
- def request(method:, params: {}, **opts)
23
- context.request(method, expand(params), **opts)
22
+ def request(method:, params: {}, **)
23
+ context.request(method, expand(params), **)
24
24
  end
25
25
 
26
- def get(data = {}, params: {}, **opts)
27
- request(**opts, method: :get, params: data.merge(params))
26
+ def get(data = {}, params: {}, **)
27
+ request(**, method: :get, params: data.merge(params))
28
28
  end
29
29
 
30
- def head(data = {}, params: {}, **opts)
31
- request(**opts, method: :head, params: data.merge(params))
30
+ def head(data = {}, params: {}, **)
31
+ request(**, method: :head, params: data.merge(params))
32
32
  end
33
33
 
34
- def delete(data = {}, params: {}, **opts)
35
- request(**opts, method: :delete, params: data.merge(params))
34
+ def delete(data = {}, params: {}, **)
35
+ request(**, method: :delete, params: data.merge(params))
36
36
  end
37
37
 
38
38
  def post(data = nil, **opts)
@@ -55,13 +55,15 @@ module Restify
55
55
  end
56
56
 
57
57
  def expand(params)
58
- params = convert params
59
- variables = extract! params
58
+ params = convert(params)
59
+ variables = extract!(params)
60
60
 
61
- uri = template.expand variables
62
- uri.query_values = (uri.query_values || {}).merge params if params.any?
61
+ return expand_static(params) if template.variables.empty?
63
62
 
64
- context.join uri
63
+ uri = template.expand(variables)
64
+ uri.query_values = (uri.query_values || {}).merge(params) if params.any?
65
+
66
+ context.join(uri)
65
67
  end
66
68
 
67
69
  def pattern
@@ -74,6 +76,18 @@ module Restify
74
76
 
75
77
  private
76
78
 
79
+ # A template without variables always expands to the same URI. It
80
+ # can be parsed and joined only once. The URI is frozen, as all
81
+ # requests share it.
82
+ def expand_static(params)
83
+ @static_uri ||= context.join(template.pattern).freeze
84
+ return @static_uri if params.empty?
85
+
86
+ @static_uri.dup.tap do |uri|
87
+ uri.query_values = (uri.query_values || {}).merge(params)
88
+ end
89
+ end
90
+
77
91
  def convert(params)
78
92
  params.each_pair.with_object({}) do |param, hash|
79
93
  hash[param[0]] = convert_param param[1]
@@ -11,7 +11,7 @@ module Restify
11
11
 
12
12
  # Request URI.
13
13
  #
14
- # @return [String] Request URI.
14
+ # @return [Addressable::URI] Request URI.
15
15
  #
16
16
  attr_reader :uri
17
17
 
@@ -30,7 +30,7 @@ module Restify
30
30
  attr_reader :timeout
31
31
 
32
32
  def initialize(uri:, method: :get, data: nil, timeout: 300, headers: {})
33
- @uri = uri
33
+ @uri = uri.is_a?(Addressable::URI) ? uri : Addressable::URI.parse(uri)
34
34
  @method = method.to_s.downcase
35
35
  @data = data
36
36
  @timeout = timeout
@@ -6,12 +6,13 @@ module Restify
6
6
  class Resource < SimpleDelegator
7
7
  # @api private
8
8
  #
9
- def initialize(context, response: nil, data: nil, relations: {})
9
+ def initialize(context, response: nil, data: nil, relations: {}, error: nil)
10
10
  super(data)
11
11
 
12
12
  @context = context
13
13
  @response = response
14
14
  @relations = relations
15
+ @error = error
15
16
  end
16
17
 
17
18
  # Check if resource has a relation with given name.
@@ -42,14 +43,31 @@ module Restify
42
43
 
43
44
  alias rel relation
44
45
 
46
+ # @api private
47
+ def __getobj__
48
+ # `Delegator` removes `Kernel#raise`, and the cause can only be
49
+ # set when raising.
50
+ ::Kernel.raise ParseError.new(@response, @error.message), cause: @error if @error
51
+
52
+ super
53
+ end
54
+
45
55
  # @!method data
46
56
  #
47
57
  # Return response data. Usually a hash or array.
48
58
  #
49
59
  # @return [Object] Response data.
60
+ # @raise [ParseError] If the response body could not be parsed.
50
61
  #
51
62
  alias data __getobj__
52
63
 
64
+ # @api private
65
+ def respond_to_missing?(name, include_private = false)
66
+ return false if @error
67
+
68
+ super
69
+ end
70
+
53
71
  # @!method response
54
72
  #
55
73
  # Return response if available.
@@ -95,7 +113,7 @@ module Restify
95
113
  # @api private
96
114
  def inspect
97
115
  text = {
98
- '@data' => data,
116
+ (@error ? '@error' : '@data') => @error || data,
99
117
  '@relations' => @relations,
100
118
  }.map {|k, v| "#{k}=#{v.inspect}" }.join(' ')
101
119
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'rack/media_type'
3
4
  require 'rack/utils'
4
5
  require 'json'
5
6
 
@@ -30,6 +31,10 @@ module Restify
30
31
 
31
32
  # Response body as string.
32
33
  #
34
+ # The body is encoded according to the charset from the response's
35
+ # `Content-Type` header. Without a charset, or with one unknown to
36
+ # Ruby, the body is returned as binary.
37
+ #
33
38
  # @return [String] Response body.
34
39
  #
35
40
  attr_reader :body
@@ -81,7 +86,7 @@ module Restify
81
86
  @code = code
82
87
  @status = STATUS_CODE_TO_SYMBOL[code]
83
88
  @headers = convert_headers(headers)
84
- @body = body
89
+ @body = encode_body(body)
85
90
  @message = Rack::Utils::HTTP_STATUS_CODES[code]
86
91
  end
87
92
 
@@ -112,6 +117,24 @@ module Restify
112
117
  headers['CONTENT_TYPE']
113
118
  end
114
119
 
120
+ # Return the encoding from the response's content type, if any.
121
+ #
122
+ # @return [Encoding, nil] Encoding, or nil if the content type
123
+ # carries no charset or an encoding unknown to Ruby.
124
+ #
125
+ def charset
126
+ return @charset if defined?(@charset)
127
+
128
+ charset = Rack::MediaType.params(content_type.to_s)['charset']
129
+
130
+ @charset = begin
131
+ Encoding.find(charset) if charset
132
+ rescue ArgumentError
133
+ # Unknown: keep the body as binary rather than guessing.
134
+ nil
135
+ end
136
+ end
137
+
115
138
  # Check if response is successful e.g. the status code
116
139
  # is on of 2XX.
117
140
  #
@@ -130,13 +153,20 @@ module Restify
130
153
  (400...600).cover? code
131
154
  end
132
155
 
156
+ # Decoded body for error messages, or nil if the body is empty or
157
+ # could not be parsed.
158
+ #
133
159
  # @api private
134
160
  def decoded_body
135
- @decoded_body ||= begin
161
+ return @decoded_body if defined?(@decoded_body)
162
+
163
+ @decoded_body = begin
136
164
  case content_type
137
165
  when %r{\Aapplication/json($|;)}
138
- ::JSON.parse(body)
166
+ ::JSON.parse(body) unless body.nil? || body.empty?
139
167
  end
168
+ rescue ::JSON::ParserError
169
+ nil
140
170
  end
141
171
  end
142
172
 
@@ -147,6 +177,12 @@ module Restify
147
177
 
148
178
  private
149
179
 
180
+ def encode_body(body)
181
+ return body unless body && charset
182
+
183
+ (+body).force_encoding(charset)
184
+ end
185
+
150
186
  def convert_headers(headers)
151
187
  headers.each.to_h do |pair|
152
188
  [pair[0].upcase, pair[1]]