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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6db18b2b418bd450289f6e0c64f429e0c3f3066e6a45c082af2f34fa87b3219f
4
- data.tar.gz: ca2c0abe620c7a679e0c0ec0b12e57448ea7d709b5e0ee18d523183e97085715
3
+ metadata.gz: d8fa974df7fe0cd06afd87ffe8bef89d87559ece784a801070aa0b8342b883eb
4
+ data.tar.gz: 1f3cc52e1b4bf7995f50dbe5ef9360986d71c7d157b47b1527f79cca2ff66179
5
5
  SHA512:
6
- metadata.gz: 85f715444240b80d9a269662779b69791a9240b6d37d69586948aaed3c138c8b8bc11d78e0eadd42d7da600f65e628b589053f33991b66a0a9b2c923248b4999
7
- data.tar.gz: 9a1509904bcc695749e2cfb2b95976a38a94013db01aabb7374c270de7af788ac1f8e633a846aafe56e14b3cbb244d3952a99c769d845ea3c68c57f8d4f66c8e
6
+ metadata.gz: cdc59e5ca15e38eb8224b638e675807f669cf0ee00802427334238107ce8a5cddc16b71cbec12e0013055fe5431b38033509381f4c1a1727e07c38cfed1be4fc
7
+ data.tar.gz: 7da86c8fb8dbf2829da84e9f3e0c5d47d226e360a35cc7b6b33e1da02d7507ef4570d703d31c3ef7448fb3586592218d5924b926e70d1694abfdb916de6e1c4f
data/CHANGELOG.md CHANGED
@@ -17,6 +17,81 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
17
17
 
18
18
  ### Breaks
19
19
 
20
+ ## 3.0.0-rc3 - (2026-09-27)
21
+
22
+ ---
23
+
24
+ ### Changes
25
+
26
+ - Perf: Avoid parsing/joining URIs when not necessary, e.g., for relations without variables or absolute URIs.
27
+ - Perf: Reuse libcurl handles in the Ethon adapter
28
+ - Perf: Optimize libcurl option preparation in the Ethon adapter
29
+ - Perf: Optimize parsing of data and relations
30
+ - Perf: Replace `Concurrent::IVar` with a lightweight promise implementation
31
+ - Remove `concurrent-ruby` dependency
32
+
33
+ ### Fixes
34
+
35
+ - Apply `Restify::Timeout.default_timeout` when waiting on a promise without
36
+ an explicit timeout, e.g. with the Typhoeus adapter, instead of waiting forever
37
+ - A timeout while waiting on a promise does not reject the promise anymore.
38
+ Waiting again, e.g. with a longer timeout, continues where the last wait gave up.
39
+ - Honor the timeout of all threads waiting on the same promise, not only of the
40
+ thread running its `#then` blocks
41
+ - Only create relations from `*_url` fields with string values, e.g. not from numbers, objects, or arrays
42
+
43
+ ### Breaks
44
+
45
+ - `Restify::Promise` does not inherit from `Concurrent::IVar` anymore
46
+
47
+ ## 3.0.0.rc2 - (2026-09-25)
48
+
49
+ ---
50
+
51
+ First release candidate for 3.0. `3.0.0.rc1` was tagged, but not published due to a broken release pipeline.
52
+
53
+ ### New
54
+
55
+ - Add `Restify::Adapter::Ethon` adapter directly using libcurl in a background event loop
56
+ - Perf: Run event loop in waiting threads to avoid slow handover between threads
57
+ - Add `restify/webmock` to intercept requests with WebMock for all adapters
58
+
59
+ ### Changes
60
+
61
+ - Restrict requests and redirects to HTTP(S), as libcurl supports many more
62
+ protocols such as `file://` or `gopher://`
63
+ - Drop EOL Ruby versions 3.1 and 3.2
64
+ - Drop `hitimes` dependency, using `Process.clock_gettime` for timeouts
65
+ - Return resources without data for empty response bodies, even with a JSON
66
+ or MessagePack content type (#31)
67
+
68
+ ### Fixes
69
+
70
+ - Use the effective URI after redirects for responses and relations
71
+ - Do not set empty OpenTelemetry attributes for URIs without a port
72
+ - Encode response bodies using the charset from the `Content-Type` header
73
+ - Do not share connections of the Ethon adapter with forked child processes
74
+ - Do not deadlock the Typhoeus adapter with `sync: true` when used from multiple threads
75
+ - Raise the `ResponseError` for the status code, e.g. `NotFound`, instead of a
76
+ `JSON::ParserError` for error responses with an empty or invalid JSON body (#31)
77
+
78
+ ### Breaks
79
+
80
+ - Raise minimum Ruby version to 3.3
81
+ - `Restify::Request#uri` is always parsed and returned as `Addressable::URI`
82
+ - Use `Restify::Adapter::Ethon` by default. With WebMock, require
83
+ `restify/webmock`, as WebMock's Typhoeus integration does not apply anymore.
84
+ - `typhoeus` is an optional dependency now. Add it to your Gemfile to use
85
+ `Restify::Adapter::Typhoeus`.
86
+ - Invalid response bodies do not raise from `#value!` anymore. The resource is
87
+ returned, e.g. with relations from headers, and accessing its data raises a
88
+ `Restify::ParseError` with the parser error as its cause (#31).
89
+ - Remove `activesupport` dependency. `Restify.cache` defaults to `nil`, as the
90
+ previous default cache did not cache anything (yet).
91
+ - Remove `logging` dependency. Set a `Logger`-compatible logger with
92
+ `Restify.logger = Logger.new(...)`, nothing is logged by default. Restify does
93
+ not change `Ethon.logger` anymore.
94
+
20
95
  ## 2.1.0 - (2026-06-20)
21
96
 
22
97
  ---
data/README.md CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  [![Gem Version](https://img.shields.io/gem/v/restify?logo=ruby)](https://rubygems.org/gems/restify)
4
4
  [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/jgraichen/restify/test.yml?logo=github)](https://github.com/jgraichen/restify/actions)
5
- [![Code Quality](https://codebeat.co/badges/368f8033-bd76-48bc-9777-85f1d4befa94)](https://codebeat.co/projects/github-com-jgraichen-restify-main)
6
5
 
7
6
  Restify is an hypermedia REST client that does parallel, concurrent and keep-alive requests by default.
8
7
 
@@ -12,11 +11,16 @@ Restify can be used to consume hypermedia REST APIs (like GitHubs), to build a s
12
11
 
13
12
  Restify is build upon the following libraries:
14
13
 
15
- * [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby)
16
14
  * [addressable](https://github.com/sporkmonger/addressable)
17
- * [typhoeus](https://github.com/typhoeus/typhoeus)
15
+ * [ethon](https://github.com/typhoeus/ethon), using libcurl
18
16
 
19
- The HTTP adapters are mostly run in a background thread and may not survive mid-application forks.
17
+ Restify uses libcurl via Ethon by default. The older [Typhoeus](https://github.com/typhoeus/typhoeus) adapter is still available. To use it, add `typhoeus` to your Gemfile and configure it:
18
+
19
+ ```ruby
20
+ Restify.adapter = Restify::Adapter::Typhoeus.new
21
+ ```
22
+
23
+ The default Ethon adapter should work with forking, e.g. in Puma or Unicorn workers. Requests still pending when forking are aborted in the child process.
20
24
 
21
25
  Restify includes processors to parse responses and to extract links between resources. The following formats are can be parsed:
22
26
 
@@ -102,6 +106,27 @@ puts "#{commit['commit']['message']}"
102
106
 
103
107
  See commented example in main spec [`spec/restify_spec.rb`](https://github.com/jgraichen/restify/blob/master/spec/restify_spec.rb#L100) or in the `examples` directory.
104
108
 
109
+ ## Testing with WebMock
110
+
111
+ Restify can intercept requests for [WebMock](https://github.com/bblimke/webmock), independent of the adapter in use. Load it after WebMock, before WebMock is enabled, e.g. in your `spec_helper.rb`:
112
+
113
+ ```ruby
114
+ require 'webmock/rspec'
115
+ require 'restify/webmock'
116
+ ```
117
+
118
+ Note: Stubbed responses are returned as they are, including redirects, like WebMock does for other libcurl-based libraries. Requests not stubbed are passed on to the adapter if WebMock allows connections.
119
+
120
+ For tests relying on actual HTTP behavior, e.g. following redirects, run a local stub server instead, so that requests are sent over the network by the adapter.
121
+
122
+ WebMock's own Typhoeus integration is not needed for Restify and can be disabled. Otherwise, requests are reported a second time, when they are passed on to the Typhoeus adapter:
123
+
124
+ ```ruby
125
+ WebMock.enable!(except: %i[typhoeus])
126
+ ```
127
+
128
+ Keep it enabled if your application uses Typhoeus directly, too.
129
+
105
130
  ## Contributing
106
131
 
107
132
  1. [Fork it](http://github.com/jgraichen/restify/fork)
@@ -113,7 +138,7 @@ See commented example in main spec [`spec/restify_spec.rb`](https://github.com/j
113
138
 
114
139
  ## License
115
140
 
116
- Copyright (C) 2014-2025 Jan Graichen
141
+ Copyright (C) 2014-2026 Jan Graichen
117
142
 
118
143
  This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
119
144
 
@@ -8,7 +8,7 @@ module Restify
8
8
  prepend Telemetry
9
9
 
10
10
  def call(request)
11
- Promise.create do |writer|
11
+ Promise.create(driver:) do |writer|
12
12
  call_native request, writer
13
13
  end
14
14
  end
@@ -16,6 +16,15 @@ module Restify
16
16
  def call_native(_request, _writer)
17
17
  raise NotImplementedError.new 'Subclass responsibility'
18
18
  end
19
+
20
+ private
21
+
22
+ # Adapters that can process requests in the thread waiting on the
23
+ # returned promise return an object responding to
24
+ # `#drive(promise, timeout)` here, see `Promise#wait`.
25
+ def driver
26
+ nil
27
+ end
19
28
  end
20
29
  end
21
30
  end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Restify
4
+ module Adapter
5
+ class Ethon < Base
6
+ #
7
+ # A libcurl easy handle for Restify requests, translating a
8
+ # request into libcurl options, and a completed transfer back into
9
+ # a response.
10
+ #
11
+ # Handles are reused for many requests, see `Pool`.
12
+ #
13
+ # @api private
14
+ #
15
+ class Easy < ::Ethon::Easy
16
+ attr_accessor :_otel_span, :_restify_request, :_restify_writer
17
+
18
+ TIMEOUT_MS = Options.number(:timeout_ms)
19
+ CONNECTTIMEOUT_MS = Options.number(:connecttimeout_ms)
20
+
21
+ def reset
22
+ @_otel_span = @_restify_request = @_restify_writer = nil
23
+
24
+ super
25
+ end
26
+
27
+ def prepare(request, options)
28
+ options.apply(self)
29
+
30
+ self.url = request.uri.to_s
31
+ self.headers = DEFAULT_HEADERS.merge(request.headers)
32
+
33
+ if request.timeout
34
+ # libcurl requires millisecond-based timeouts so fractional
35
+ # seconds must be converted and rounded.
36
+ timeout = (request.timeout * 1000).ceil
37
+ ::Ethon::Curl.easy_setopt(handle, TIMEOUT_MS, :long, timeout)
38
+ ::Ethon::Curl.easy_setopt(handle, CONNECTTIMEOUT_MS, :long, timeout)
39
+ end
40
+
41
+ prepare_method(request.method, request.body)
42
+
43
+ # The OpenTelemetry instrumentation for Ethon takes the method
44
+ # from `#http_request`, which is not used here.
45
+ @otel_method = request.method
46
+ end
47
+
48
+ def response(request)
49
+ code = return_code
50
+ if code != :ok
51
+ raise Restify::NetworkError.new(request, ::Ethon::Curl.easy_strerror(code))
52
+ end
53
+
54
+ # Non-HTTP protocols are refused before a transfer is started,
55
+ # but libcurl still reports success with a zero status code
56
+ # whenever no HTTP status was received, and no status code at
57
+ # all when it cannot be read back.
58
+ status = response_code
59
+ if status.nil? || status.zero?
60
+ raise Restify::NetworkError.new(request, 'Response without HTTP status')
61
+ end
62
+
63
+ ::Restify::Response.new(
64
+ request,
65
+ effective_uri(request),
66
+ status,
67
+ parse_headers(response_headers),
68
+ response_body,
69
+ )
70
+ end
71
+
72
+ private
73
+
74
+ # Like Ethon's actions for each method, see `Ethon::Easy::Http`.
75
+ # An empty body counts as no body.
76
+ def prepare_method(method, body)
77
+ body = nil if body.nil? || body.empty?
78
+
79
+ case method
80
+ when 'get'
81
+ if body
82
+ postfields(body)
83
+ self.customrequest = 'GET'
84
+ end
85
+ when 'post'
86
+ postfields(body || '')
87
+ when 'put'
88
+ self.upload = true
89
+ self.infilesize = body ? body.bytesize : 0
90
+ set_read_callback(body) if body
91
+ when 'head'
92
+ postfields(body) if body
93
+ self.nobody = true
94
+ else
95
+ postfields(body) if body
96
+ self.customrequest = method.upcase
97
+ end
98
+ end
99
+
100
+ def postfields(body)
101
+ self.postfieldsize = body.bytesize
102
+ self.copypostfields = body
103
+ end
104
+
105
+ def effective_uri(request)
106
+ url = effective_url
107
+
108
+ # Only parse the URL after redirects, as parsing is expensive.
109
+ return request.uri if url.nil? || url == request.uri.to_s
110
+
111
+ Addressable::URI.parse(url)
112
+ end
113
+
114
+ def parse_headers(raw)
115
+ headers = {}
116
+ return headers if raw.nil?
117
+
118
+ # The raw headers can contain multiple blocks, e.g. from
119
+ # informational responses or when following redirects; only
120
+ # use the latest ones:
121
+ block = raw.split(/\r?\n\r?\n/).reject {|b| b.strip.empty? }.last
122
+
123
+ # Split on newlines that are not followed by whitespace to
124
+ # keep folded header values together.
125
+ block.to_s.split(/\r?\n(?!\s)/).each do |line|
126
+ line = line.strip
127
+ next if line.empty? || line.start_with?('HTTP/')
128
+
129
+ key, value = line.split(':', 2)
130
+ next if value.nil?
131
+
132
+ key = key.strip.upcase.tr('-', '_')
133
+ value = value.strip.gsub(/\r?\n\s*/, ' ')
134
+
135
+ case (current = headers[key])
136
+ when nil then headers[key] = value
137
+ when Array then current << value
138
+ else headers[key] = [current, value]
139
+ end
140
+ end
141
+
142
+ headers
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,255 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Restify
4
+ module Adapter
5
+ class Ethon < Base
6
+ #
7
+ # An event loop running libcurl's multi interface in
8
+ # `socket_action` mode, waiting for sockets with nio4r.
9
+ #
10
+ # Can woken up to add new easy handles immediately but can run
11
+ # from any thread. `LoopLock` ensures all interaction with libcurl
12
+ # is still serialized and threads are correctly prioritized.
13
+ #
14
+ # @api private
15
+ #
16
+ class EventLoop
17
+ include Logging
18
+
19
+ INTERESTS = {
20
+ none: nil,
21
+ in: :r,
22
+ out: :w,
23
+ inout: :rw,
24
+ }.freeze
25
+
26
+ def initialize(**multi)
27
+ @selector = NIO::Selector.new
28
+ @monitors = {}
29
+ @timer = nil
30
+ @queue = Queue.new
31
+
32
+ # libcurl only stores the function pointers, therefore the procs
33
+ # must be referenced here too, or they would be garbage
34
+ # collected.
35
+ @socketfunction = method(:on_socket).to_proc
36
+ @timerfunction = method(:on_timer).to_proc
37
+
38
+ @multi = ::Ethon::Multi.new(execution_mode: :socket_action, **multi)
39
+ @multi.socketfunction = @socketfunction
40
+ @multi.timerfunction = @timerfunction
41
+
42
+ # Wake up the background thread from `select` when a waiting
43
+ # thread wants to run the loop.
44
+ @lock = LoopLock.new { @selector.wakeup }
45
+ end
46
+
47
+ def enqueue(easy)
48
+ @queue << easy
49
+ @selector.wakeup
50
+ end
51
+
52
+ # Wake up threads waiting for the loop, e.g. after completing a
53
+ # transfer, to check if their result is available now.
54
+ def notify
55
+ @lock.notify
56
+ end
57
+
58
+ # Run the loop in the calling thread until the promise is
59
+ # complete or the timeout expires.
60
+ #
61
+ # Several threads can wait at the same time, but only one runs
62
+ # the loop. The others sleep until either their promise
63
+ # completes or the loop is released.
64
+ #
65
+ # @return [Boolean] false when the loop cannot be run in the
66
+ # calling thread, i.e. when waiting from within the loop
67
+ # itself.
68
+ #
69
+ def drive(promise, timeout) # rubocop:disable Naming/PredicateMethod
70
+ return false if @lock.owned?
71
+
72
+ while @lock.acquire(timeout) { promise.complete? }
73
+ begin
74
+ step(timeout.remaining) until promise.complete? || !timeout.remaining.positive?
75
+ rescue StandardError => e
76
+ error(e)
77
+ ensure
78
+ @lock.release
79
+ end
80
+ end
81
+
82
+ true
83
+ end
84
+
85
+ # Run the loop whenever no thread waits for it. Blocks forever,
86
+ # as meant for a background thread.
87
+ def run
88
+ loop do
89
+ @lock.acquire_background
90
+
91
+ begin
92
+ step
93
+ ensure
94
+ @lock.release
95
+ end
96
+ rescue StandardError => e
97
+ error(e)
98
+ end
99
+ ensure
100
+ debug 'loop:exit'
101
+ end
102
+
103
+ # Forget the loop in a forked child process, without releasing
104
+ # its libcurl handles, as they belong to the parent process.
105
+ # Transfers not completed yet are rejected, as they were started
106
+ # by the parent process too.
107
+ #
108
+ def forked!
109
+ pending = @multi.easy_handles.dup
110
+ pending << @queue.pop(true) until @queue.empty?
111
+
112
+ @multi.handle.autorelease = false
113
+
114
+ pending.each do |easy|
115
+ easy.handle.autorelease = false
116
+ easy._restify_writer.reject(
117
+ Restify::NetworkError.new(
118
+ easy._restify_request,
119
+ 'Request started before fork',
120
+ ),
121
+ )
122
+ end
123
+
124
+ nil
125
+ end
126
+
127
+ private
128
+
129
+ def step(limit = nil)
130
+ dequeue_all
131
+
132
+ # libcurl needs to be notified about its own timeouts, e.g. to
133
+ # start newly added transfers or to time out stalled ones.
134
+ timeout!
135
+
136
+ timeout = select_timeout
137
+ timeout = limit if limit && (timeout.nil? || timeout > limit)
138
+ debug 'loop:select', timeout: timeout
139
+
140
+ # nil on timeout; empty array when woken up
141
+ @selector.select(timeout)&.each do |monitor|
142
+ # libcurl can remove sockets while earlier events of the
143
+ # same batch are processed, e.g. when a completed transfer
144
+ # tears down other connections. Skip monitors that are gone
145
+ # by now.
146
+ next unless @monitors[monitor.value].equal?(monitor)
147
+
148
+ socket_action(monitor.value, readiness(monitor))
149
+ end
150
+ end
151
+
152
+ def dequeue_all
153
+ loop do
154
+ easy = begin
155
+ @queue.pop(true)
156
+ rescue ThreadError
157
+ break
158
+ end
159
+
160
+ add(easy)
161
+ end
162
+ end
163
+
164
+ def add(easy)
165
+ # Restore the OpenTelemetry span the request originated from,
166
+ # so that the Ethon instrumentation picks up the correct
167
+ # parent when the easy handle is added to libcurl.
168
+ #
169
+ # Handle exceptions from Ethon or WebMock too, and reject the
170
+ # promise, so that the errors can be handled in user code.
171
+ OpenTelemetry::Trace.with_span(easy._otel_span) do
172
+ @multi.add(easy)
173
+ rescue Exception => e # rubocop:disable Lint/RescueException
174
+ easy._restify_writer.reject(e)
175
+ end
176
+ end
177
+
178
+ # Seconds until libcurl wants to be called again, or nil to
179
+ # block until a socket becomes ready or the loop is woken up.
180
+ def select_timeout
181
+ return nil unless @timer
182
+
183
+ [@timer - now, 0].max
184
+ end
185
+
186
+ def timeout!
187
+ return unless @timer
188
+ return if now < @timer
189
+
190
+ # Reset before invoking libcurl, as it will set a new timeout
191
+ # from within the socket action.
192
+ @timer = nil
193
+
194
+ socket_action
195
+ end
196
+
197
+ def readiness(monitor)
198
+ readiness = []
199
+ readiness << :in if monitor.readable?
200
+ readiness << :out if monitor.writable?
201
+ readiness
202
+ end
203
+
204
+ def socket_action(socket = nil, readiness = 0)
205
+ @multi.socket_action(socket, readiness)
206
+ end
207
+
208
+ # libcurl: called when asking for readiness monitoring
209
+ def on_socket(_easy, socket, what, _userp, _socketp)
210
+ debug 'socket:action', tag: socket, what: what
211
+
212
+ if what == :remove
213
+ @monitors.delete(socket)&.close
214
+ else
215
+ monitor = (@monitors[socket] ||= register(socket))
216
+ monitor.interests = INTERESTS.fetch(what)
217
+ end
218
+
219
+ :ok
220
+ rescue StandardError => e
221
+ error(e)
222
+ :ok
223
+ end
224
+
225
+ # libcurl: called on timeout changes
226
+ def on_timer(_handle, timeout_ms, _userp)
227
+ debug 'timer:set', timeout: timeout_ms
228
+ @timer = timeout_ms.negative? ? nil : now + (timeout_ms / 1000.0)
229
+
230
+ :ok
231
+ rescue StandardError => e
232
+ error(e)
233
+ :ok
234
+ end
235
+
236
+ def register(socket)
237
+ # FD owned by libcurl: IO/ruby must never close it
238
+ io = IO.for_fd(socket, autoclose: false)
239
+
240
+ @selector.register(io, :r).tap do |monitor|
241
+ monitor.value = socket
242
+ end
243
+ end
244
+
245
+ def now
246
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
247
+ end
248
+
249
+ def _log_prefix
250
+ "[#{object_id}/#{Thread.current.object_id}]"
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Restify
4
+ module Adapter
5
+ class Ethon < Base
6
+ #
7
+ # libcurl options, converted to the arguments for
8
+ # `curl_easy_setopt` once for performance reasons.
9
+ #
10
+ # @api private
11
+ #
12
+ class Options
13
+ DEFINITIONS = ::Ethon::Curl.easy_options(nil)
14
+
15
+ class << self
16
+ def number(name)
17
+ definition(name).fetch(:opt)
18
+ end
19
+
20
+ def definition(name)
21
+ DEFINITIONS.fetch(name) do
22
+ raise ArgumentError.new("Unknown libcurl option: #{name}")
23
+ end
24
+ end
25
+ end
26
+
27
+ # Options by name, e.g. `followlocation: true`, nil values are
28
+ # skipped.
29
+ #
30
+ def initialize(options)
31
+ @options = options.filter_map do |name, value|
32
+ definition = self.class.definition(name)
33
+ next if value.nil?
34
+
35
+ compile(name, definition, value)
36
+ end.freeze
37
+ end
38
+
39
+ def apply(easy)
40
+ handle = easy.handle
41
+
42
+ @options.each do |opt, type, value|
43
+ if type
44
+ ::Ethon::Curl.easy_setopt(handle, opt, type, value)
45
+ else
46
+ ::Ethon::Curl.set_option(opt, value, handle)
47
+ end
48
+ end
49
+ end
50
+
51
+ def to_a
52
+ @options
53
+ end
54
+
55
+ private
56
+
57
+ # Like `Ethon::Curls::Options#set_option` for the supported types.
58
+ def compile(name, definition, value)
59
+ opt = definition[:opt]
60
+
61
+ case definition[:type]
62
+ when :bool then [opt, :long, value && value != 0 ? 1 : 0]
63
+ when :int, :time then [opt, :long, Integer(value)]
64
+ when :enum then [opt, :long, lookup(name, definition, value)]
65
+ when :bitmask then [opt, :long, Array(value).reduce(0) {|mask, v| mask | lookup(name, definition, v) }]
66
+ when :string then [opt, :string, value.to_s]
67
+ else [name, nil, value]
68
+ end
69
+ end
70
+
71
+ def lookup(name, definition, value)
72
+ return Integer(value) unless value.is_a?(Symbol) || value.is_a?(String)
73
+
74
+ definition[:opts].fetch(value.to_sym) do
75
+ raise ArgumentError.new("Unknown value for libcurl option #{name}: #{value.inspect}")
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end