http 5.3.1 → 6.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 (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -41
  3. data/LICENSE.txt +1 -1
  4. data/README.md +110 -13
  5. data/UPGRADING.md +491 -0
  6. data/http.gemspec +32 -29
  7. data/lib/http/base64.rb +11 -1
  8. data/lib/http/chainable/helpers.rb +62 -0
  9. data/lib/http/chainable/verbs.rb +136 -0
  10. data/lib/http/chainable.rb +232 -136
  11. data/lib/http/client.rb +158 -127
  12. data/lib/http/connection/internals.rb +141 -0
  13. data/lib/http/connection.rb +126 -97
  14. data/lib/http/content_type.rb +61 -6
  15. data/lib/http/errors.rb +25 -1
  16. data/lib/http/feature.rb +65 -5
  17. data/lib/http/features/auto_deflate.rb +124 -17
  18. data/lib/http/features/auto_inflate.rb +38 -15
  19. data/lib/http/features/caching/entry.rb +178 -0
  20. data/lib/http/features/caching/in_memory_store.rb +63 -0
  21. data/lib/http/features/caching.rb +216 -0
  22. data/lib/http/features/digest_auth.rb +234 -0
  23. data/lib/http/features/instrumentation.rb +97 -17
  24. data/lib/http/features/logging.rb +183 -5
  25. data/lib/http/features/normalize_uri.rb +17 -0
  26. data/lib/http/features/raise_error.rb +18 -3
  27. data/lib/http/form_data/composite_io.rb +106 -0
  28. data/lib/http/form_data/file.rb +95 -0
  29. data/lib/http/form_data/multipart/param.rb +62 -0
  30. data/lib/http/form_data/multipart.rb +106 -0
  31. data/lib/http/form_data/part.rb +52 -0
  32. data/lib/http/form_data/readable.rb +58 -0
  33. data/lib/http/form_data/urlencoded.rb +175 -0
  34. data/lib/http/form_data/version.rb +8 -0
  35. data/lib/http/form_data.rb +102 -0
  36. data/lib/http/headers/known.rb +3 -0
  37. data/lib/http/headers/normalizer.rb +17 -36
  38. data/lib/http/headers.rb +172 -65
  39. data/lib/http/mime_type/adapter.rb +24 -9
  40. data/lib/http/mime_type/json.rb +19 -4
  41. data/lib/http/mime_type.rb +21 -3
  42. data/lib/http/options/definitions.rb +189 -0
  43. data/lib/http/options.rb +172 -125
  44. data/lib/http/redirector.rb +80 -75
  45. data/lib/http/request/body.rb +87 -6
  46. data/lib/http/request/builder.rb +184 -0
  47. data/lib/http/request/proxy.rb +83 -0
  48. data/lib/http/request/writer.rb +76 -16
  49. data/lib/http/request.rb +214 -98
  50. data/lib/http/response/body.rb +103 -18
  51. data/lib/http/response/inflater.rb +35 -7
  52. data/lib/http/response/parser.rb +98 -4
  53. data/lib/http/response/status/reasons.rb +2 -4
  54. data/lib/http/response/status.rb +141 -31
  55. data/lib/http/response.rb +219 -61
  56. data/lib/http/retriable/delay_calculator.rb +38 -11
  57. data/lib/http/retriable/errors.rb +21 -0
  58. data/lib/http/retriable/performer.rb +82 -38
  59. data/lib/http/session.rb +280 -0
  60. data/lib/http/timeout/global.rb +147 -34
  61. data/lib/http/timeout/null.rb +155 -9
  62. data/lib/http/timeout/per_operation.rb +139 -18
  63. data/lib/http/uri/normalizer.rb +82 -0
  64. data/lib/http/uri/parsing.rb +182 -0
  65. data/lib/http/uri.rb +289 -124
  66. data/lib/http/version.rb +2 -1
  67. data/lib/http.rb +11 -2
  68. data/sig/deps.rbs +122 -0
  69. data/sig/http.rbs +1619 -0
  70. data/test/http/base64_test.rb +28 -0
  71. data/test/http/client_test.rb +739 -0
  72. data/test/http/connection_test.rb +1533 -0
  73. data/test/http/content_type_test.rb +190 -0
  74. data/test/http/errors_test.rb +28 -0
  75. data/test/http/feature_test.rb +49 -0
  76. data/test/http/features/auto_deflate_test.rb +317 -0
  77. data/test/http/features/auto_inflate_test.rb +213 -0
  78. data/test/http/features/caching_test.rb +942 -0
  79. data/test/http/features/digest_auth_test.rb +996 -0
  80. data/test/http/features/instrumentation_test.rb +246 -0
  81. data/test/http/features/logging_test.rb +654 -0
  82. data/test/http/features/normalize_uri_test.rb +41 -0
  83. data/test/http/features/raise_error_test.rb +77 -0
  84. data/test/http/form_data/composite_io_test.rb +215 -0
  85. data/test/http/form_data/file_test.rb +255 -0
  86. data/test/http/form_data/fixtures/the-http-gem.info +1 -0
  87. data/test/http/form_data/multipart_test.rb +303 -0
  88. data/test/http/form_data/part_test.rb +90 -0
  89. data/test/http/form_data/urlencoded_test.rb +164 -0
  90. data/test/http/form_data_test.rb +232 -0
  91. data/test/http/headers/normalizer_test.rb +93 -0
  92. data/test/http/headers_test.rb +888 -0
  93. data/test/http/mime_type/json_test.rb +39 -0
  94. data/test/http/mime_type_test.rb +150 -0
  95. data/test/http/options/base_uri_test.rb +148 -0
  96. data/test/http/options/body_test.rb +21 -0
  97. data/test/http/options/features_test.rb +38 -0
  98. data/test/http/options/form_test.rb +21 -0
  99. data/test/http/options/headers_test.rb +32 -0
  100. data/test/http/options/json_test.rb +21 -0
  101. data/test/http/options/merge_test.rb +78 -0
  102. data/test/http/options/new_test.rb +37 -0
  103. data/test/http/options/proxy_test.rb +32 -0
  104. data/test/http/options_test.rb +575 -0
  105. data/test/http/redirector_test.rb +639 -0
  106. data/test/http/request/body_test.rb +318 -0
  107. data/test/http/request/builder_test.rb +623 -0
  108. data/test/http/request/writer_test.rb +391 -0
  109. data/test/http/request_test.rb +1733 -0
  110. data/test/http/response/body_test.rb +292 -0
  111. data/test/http/response/parser_test.rb +105 -0
  112. data/test/http/response/status_test.rb +322 -0
  113. data/test/http/response_test.rb +502 -0
  114. data/test/http/retriable/delay_calculator_test.rb +194 -0
  115. data/test/http/retriable/errors_test.rb +71 -0
  116. data/test/http/retriable/performer_test.rb +551 -0
  117. data/test/http/session_test.rb +424 -0
  118. data/test/http/timeout/global_test.rb +239 -0
  119. data/test/http/timeout/null_test.rb +218 -0
  120. data/test/http/timeout/per_operation_test.rb +220 -0
  121. data/test/http/uri/normalizer_test.rb +89 -0
  122. data/test/http/uri_test.rb +1140 -0
  123. data/test/http/version_test.rb +15 -0
  124. data/test/http_test.rb +818 -0
  125. data/test/regression_tests.rb +27 -0
  126. data/test/support/dummy_server/encoding_routes.rb +47 -0
  127. data/test/support/dummy_server/routes.rb +201 -0
  128. data/test/support/dummy_server/servlet.rb +81 -0
  129. data/test/support/dummy_server.rb +200 -0
  130. data/{spec → test}/support/fakeio.rb +2 -2
  131. data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
  132. data/test/support/http_handling_shared/timeout_tests.rb +134 -0
  133. data/test/support/http_handling_shared.rb +11 -0
  134. data/test/support/proxy_server.rb +207 -0
  135. data/test/support/servers/runner.rb +67 -0
  136. data/{spec → test}/support/simplecov.rb +11 -2
  137. data/test/support/ssl_helper.rb +108 -0
  138. data/test/test_helper.rb +38 -0
  139. metadata +108 -168
  140. data/.github/workflows/ci.yml +0 -67
  141. data/.gitignore +0 -15
  142. data/.rspec +0 -1
  143. data/.rubocop/layout.yml +0 -8
  144. data/.rubocop/metrics.yml +0 -4
  145. data/.rubocop/rspec.yml +0 -9
  146. data/.rubocop/style.yml +0 -32
  147. data/.rubocop.yml +0 -11
  148. data/.rubocop_todo.yml +0 -219
  149. data/.yardopts +0 -2
  150. data/CHANGES_OLD.md +0 -1002
  151. data/Gemfile +0 -51
  152. data/Guardfile +0 -18
  153. data/Rakefile +0 -64
  154. data/lib/http/headers/mixin.rb +0 -34
  155. data/lib/http/retriable/client.rb +0 -37
  156. data/logo.png +0 -0
  157. data/spec/lib/http/client_spec.rb +0 -556
  158. data/spec/lib/http/connection_spec.rb +0 -88
  159. data/spec/lib/http/content_type_spec.rb +0 -47
  160. data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
  161. data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
  162. data/spec/lib/http/features/instrumentation_spec.rb +0 -81
  163. data/spec/lib/http/features/logging_spec.rb +0 -65
  164. data/spec/lib/http/features/raise_error_spec.rb +0 -62
  165. data/spec/lib/http/headers/mixin_spec.rb +0 -36
  166. data/spec/lib/http/headers/normalizer_spec.rb +0 -52
  167. data/spec/lib/http/headers_spec.rb +0 -527
  168. data/spec/lib/http/options/body_spec.rb +0 -15
  169. data/spec/lib/http/options/features_spec.rb +0 -33
  170. data/spec/lib/http/options/form_spec.rb +0 -15
  171. data/spec/lib/http/options/headers_spec.rb +0 -24
  172. data/spec/lib/http/options/json_spec.rb +0 -15
  173. data/spec/lib/http/options/merge_spec.rb +0 -68
  174. data/spec/lib/http/options/new_spec.rb +0 -30
  175. data/spec/lib/http/options/proxy_spec.rb +0 -20
  176. data/spec/lib/http/options_spec.rb +0 -13
  177. data/spec/lib/http/redirector_spec.rb +0 -530
  178. data/spec/lib/http/request/body_spec.rb +0 -211
  179. data/spec/lib/http/request/writer_spec.rb +0 -121
  180. data/spec/lib/http/request_spec.rb +0 -234
  181. data/spec/lib/http/response/body_spec.rb +0 -85
  182. data/spec/lib/http/response/parser_spec.rb +0 -74
  183. data/spec/lib/http/response/status_spec.rb +0 -253
  184. data/spec/lib/http/response_spec.rb +0 -262
  185. data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
  186. data/spec/lib/http/retriable/performer_spec.rb +0 -302
  187. data/spec/lib/http/uri/normalizer_spec.rb +0 -95
  188. data/spec/lib/http/uri_spec.rb +0 -71
  189. data/spec/lib/http_spec.rb +0 -535
  190. data/spec/regression_specs.rb +0 -24
  191. data/spec/spec_helper.rb +0 -89
  192. data/spec/support/black_hole.rb +0 -13
  193. data/spec/support/dummy_server/servlet.rb +0 -203
  194. data/spec/support/dummy_server.rb +0 -44
  195. data/spec/support/fuubar.rb +0 -21
  196. data/spec/support/http_handling_shared.rb +0 -190
  197. data/spec/support/proxy_server.rb +0 -39
  198. data/spec/support/servers/config.rb +0 -11
  199. data/spec/support/servers/runner.rb +0 -19
  200. data/spec/support/ssl_helper.rb +0 -104
  201. /data/{spec → test}/support/capture_warning.rb +0 -0
data/lib/http/headers.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  require "forwardable"
4
4
 
5
5
  require "http/errors"
6
- require "http/headers/mixin"
7
6
  require "http/headers/normalizer"
8
7
  require "http/headers/known"
9
8
 
@@ -14,32 +13,56 @@ module HTTP
14
13
  include Enumerable
15
14
 
16
15
  class << self
17
- # Coerces given `object` into Headers.
16
+ # Coerces given object into Headers
17
+ #
18
+ # @example
19
+ # headers = HTTP::Headers.coerce("Content-Type" => "text/plain")
18
20
  #
19
21
  # @raise [Error] if object can't be coerced
20
22
  # @param [#to_hash, #to_h, #to_a] object
21
23
  # @return [Headers]
24
+ # @api public
22
25
  def coerce(object)
23
- unless object.is_a? self
24
- object = case
25
- when object.respond_to?(:to_hash) then object.to_hash
26
- when object.respond_to?(:to_h) then object.to_h
27
- when object.respond_to?(:to_a) then object.to_a
28
- else raise Error, "Can't coerce #{object.inspect} to Headers"
29
- end
30
- end
26
+ object = if object.respond_to?(:to_hash) then object.to_hash
27
+ elsif object.respond_to?(:to_h) then object.to_h
28
+ elsif object.respond_to?(:to_a) then object.to_a
29
+ else raise Error, "Can't coerce #{object.inspect} to Headers"
30
+ end
31
+
31
32
  headers = new
32
33
  object.each { |k, v| headers.add k, v }
33
34
  headers
34
35
  end
36
+ # @!method [](object)
37
+ # Coerces given object into Headers
38
+ #
39
+ # @example
40
+ # headers = HTTP::Headers["Content-Type" => "text/plain"]
41
+ #
42
+ # @see .coerce
43
+ # @return [Headers]
44
+ # @api public
35
45
  alias [] coerce
36
46
 
47
+ # Returns the shared normalizer instance
48
+ #
49
+ # @example
50
+ # HTTP::Headers.normalizer
51
+ #
52
+ # @return [Headers::Normalizer]
53
+ # @api public
37
54
  def normalizer
38
- @normalizer ||= Headers::Normalizer.new
55
+ @normalizer ||= Normalizer.new #: Headers::Normalizer
39
56
  end
40
57
  end
41
58
 
42
- # Class constructor.
59
+ # Creates a new empty headers container
60
+ #
61
+ # @example
62
+ # headers = HTTP::Headers.new
63
+ #
64
+ # @return [Headers]
65
+ # @api public
43
66
  def initialize
44
67
  # The @pile stores each header value using a three element array:
45
68
  # 0 - the normalized header key, used for lookup
@@ -48,26 +71,46 @@ module HTTP
48
71
  @pile = []
49
72
  end
50
73
 
51
- # Sets header.
74
+ # Sets header, replacing any existing values
75
+ #
76
+ # @example
77
+ # headers.set("Content-Type", "text/plain")
52
78
  #
53
79
  # @param (see #add)
54
80
  # @return [void]
81
+ # @api public
55
82
  def set(name, value)
56
83
  delete(name)
57
84
  add(name, value)
58
85
  end
86
+ # @!method []=(name, value)
87
+ # Sets header, replacing any existing values
88
+ #
89
+ # @example
90
+ # headers["Content-Type"] = "text/plain"
91
+ #
92
+ # @see #set
93
+ # @return [void]
94
+ # @api public
59
95
  alias []= set
60
96
 
61
- # Removes header.
97
+ # Removes header with the given name
98
+ #
99
+ # @example
100
+ # headers.delete("Content-Type")
62
101
  #
63
102
  # @param [#to_s] name header name
64
103
  # @return [void]
104
+ # @api public
65
105
  def delete(name)
66
- name = normalize_header name.to_s
67
- @pile.delete_if { |k, _| k == name }
106
+ name = normalize_header name
107
+ @pile.delete_if { |k, _| k.eql?(name) }
68
108
  end
69
109
 
70
- # Appends header.
110
+ # Appends header value(s) to the given name
111
+ #
112
+ # @example
113
+ # headers.add("Accept", "text/html")
71
114
  #
72
115
  # @param [String, Symbol] name header name. When specified as a string, the
73
116
  # name is sent as-is. When specified as a symbol, the name is converted
@@ -77,16 +120,11 @@ module HTTP
77
120
  # is sent as `"auth_key"`.
78
121
  # @param [Array<#to_s>, #to_s] value header value(s) to be appended
79
122
  # @return [void]
123
+ # @api public
80
124
  def add(name, value)
81
- lookup_name = normalize_header(name.to_s)
82
- wire_name = case name
83
- when String
84
- name
85
- when Symbol
86
- lookup_name
87
- else
88
- raise HTTP::HeaderError, "HTTP header must be a String or Symbol: #{name.inspect}"
89
- end
125
+ lookup_name = normalize_header(name)
126
+ wire_name = wire_name_for(name, lookup_name)
127
+
90
128
  Array(value).each do |v|
91
129
  @pile << [
92
130
  lookup_name,
@@ -96,139 +134,208 @@ module HTTP
96
134
  end
97
135
  end
98
136
 
99
- # Returns list of header values if any.
137
+ # Returns list of header values if any
138
+ #
139
+ # @example
140
+ # headers.get("Content-Type")
100
141
  #
101
142
  # @return [Array<String>]
143
+ # @api public
102
144
  def get(name)
103
- name = normalize_header name.to_s
104
- @pile.select { |k, _| k == name }.map { |_, _, v| v }
145
+ name = normalize_header name
146
+ @pile.filter_map { |k, _, v| v if k.eql?(name) }
105
147
  end
106
148
 
107
- # Smart version of {#get}.
149
+ # Smart version of {#get}
150
+ #
151
+ # @example
152
+ # headers["Content-Type"]
108
153
  #
109
154
  # @return [nil] if header was not set
110
155
  # @return [String] if header has exactly one value
111
156
  # @return [Array<String>] if header has more than one value
157
+ # @api public
112
158
  def [](name)
113
159
  values = get(name)
160
+ return if values.empty?
114
161
 
115
- case values.count
116
- when 0 then nil
117
- when 1 then values.first
118
- else values
119
- end
162
+ return values unless values.one?
163
+
164
+ values.join
120
165
  end
121
166
 
122
- # Tells whenever header with given `name` is set or not.
167
+ # Tells whether header with given name is set
168
+ #
169
+ # @example
170
+ # headers.include?("Content-Type")
123
171
  #
124
172
  # @return [Boolean]
173
+ # @api public
125
174
  def include?(name)
126
- name = normalize_header name.to_s
127
- @pile.any? { |k, _| k == name }
175
+ name = normalize_header name
176
+ @pile.any? { |k, _| k.eql?(name) }
128
177
  end
129
178
 
130
179
  # Returns Rack-compatible headers Hash
131
180
  #
181
+ # @example
182
+ # headers.to_h
183
+ #
132
184
  # @return [Hash]
185
+ # @api public
133
186
  def to_h
134
187
  keys.to_h { |k| [k, self[k]] }
135
188
  end
189
+ # @!method to_hash
190
+ # @see #to_h
191
+ # @return [Hash]
136
192
  alias to_hash to_h
137
193
 
138
- # Returns headers key/value pairs.
194
+ # Pattern matching interface
139
195
  #
140
- # @return [Array<[String, String]>]
141
- def to_a
142
- @pile.map { |item| item[1..2] }
196
+ # @example
197
+ # headers.deconstruct_keys(%i[content_type])
198
+ #
199
+ # @param keys [Array<Symbol>, nil] keys to extract, or nil for all
200
+ # @return [Hash{Symbol => Object}]
201
+ # @api public
202
+ def deconstruct_keys(keys)
203
+ hash = @pile.map { |_, k, _| k }.to_h { |k| [k.tr("A-Z-", "a-z_").to_sym, self[k]] }
204
+ keys ? hash.slice(*keys) : hash
143
205
  end
144
206
 
145
- # Returns human-readable representation of `self` instance.
207
+ # Returns human-readable representation of self instance
208
+ #
209
+ # @example
210
+ # headers.inspect
146
211
  #
147
212
  # @return [String]
148
- def inspect
149
- "#<#{self.class} #{to_h.inspect}>"
150
- end
213
+ # @api public
214
+ def inspect = "#<#{self.class}>"
151
215
 
152
- # Returns list of header names.
216
+ # Returns list of header names
217
+ #
218
+ # @example
219
+ # headers.keys
153
220
  #
154
221
  # @return [Array<String>]
222
+ # @api public
155
223
  def keys
156
224
  @pile.map { |_, k, _| k }.uniq
157
225
  end
158
226
 
159
- # Compares headers to another Headers or Array of key/value pairs
227
+ # Compares headers to another Headers or Array of pairs
228
+ #
229
+ # @example
230
+ # headers == other_headers
160
231
  #
161
232
  # @return [Boolean]
233
+ # @api public
162
234
  def ==(other)
163
235
  return false unless other.respond_to? :to_a
164
236
 
165
- to_a == other.to_a
237
+ to_a.eql?(other.to_a)
166
238
  end
167
239
 
168
- # Calls the given block once for each key/value pair in headers container.
240
+ # Calls the given block once for each key/value pair
241
+ #
242
+ # @example
243
+ # headers.each { |name, value| puts "#{name}: #{value}" }
169
244
  #
170
245
  # @return [Enumerator] if no block given
171
246
  # @return [Headers] self-reference
247
+ # @api public
172
248
  def each
173
- return to_enum(__method__) unless block_given?
249
+ return to_enum unless block_given?
174
250
 
175
- @pile.each { |item| yield(item[1..2]) }
251
+ @pile.each { |item| yield(item.drop(1)) }
176
252
  self
177
253
  end
178
254
 
179
255
  # @!method empty?
180
- # Returns `true` if `self` has no key/value pairs
256
+ # Returns true if self has no key/value pairs
257
+ #
258
+ # @example
259
+ # headers.empty?
181
260
  #
182
261
  # @return [Boolean]
262
+ # @api public
183
263
  def_delegator :@pile, :empty?
184
264
 
185
265
  # @!method hash
186
- # Compute a hash-code for this headers container.
187
- # Two containers with the same content will have the same hash code.
266
+ # Computes a hash-code for this headers container
267
+ #
268
+ # @example
269
+ # headers.hash
188
270
  #
189
271
  # @see http://www.ruby-doc.org/core/Object.html#method-i-hash
190
272
  # @return [Fixnum]
273
+ # @api public
191
274
  def_delegator :@pile, :hash
192
275
 
193
- # Properly clones internal key/value storage.
276
+ # Properly clones internal key/value storage
194
277
  #
278
+ # @return [void]
195
279
  # @api private
196
- def initialize_copy(orig)
197
- super
280
+ def initialize_copy(_orig)
198
281
  @pile = @pile.map(&:dup)
199
282
  end
200
283
 
201
- # Merges `other` headers into `self`.
284
+ # Merges other headers into self
285
+ #
286
+ # @example
287
+ # headers.merge!("Accept" => "text/html")
202
288
  #
203
289
  # @see #merge
204
290
  # @return [void]
291
+ # @api public
205
292
  def merge!(other)
206
- self.class.coerce(other).to_h.each { |name, values| set name, values }
293
+ coerced = self.class.coerce(other)
294
+ names = coerced.keys
295
+ names.each { |name| set name, coerced.get(name) }
207
296
  end
208
297
 
209
- # Returns new instance with `other` headers merged in.
298
+ # Returns new instance with other headers merged in
299
+ #
300
+ # @example
301
+ # new_headers = headers.merge("Accept" => "text/html")
210
302
  #
211
303
  # @see #merge!
212
304
  # @return [Headers]
305
+ # @api public
213
306
  def merge(other)
214
307
  dup.tap { |dupped| dupped.merge! other }
215
308
  end
216
309
 
217
310
  private
218
311
 
219
- # Transforms `name` to canonical HTTP header capitalization
220
- def normalize_header(name)
221
- self.class.normalizer.call(name)
312
+ # Returns the wire name for a header
313
+ #
314
+ # @return [String]
315
+ # @api private
316
+ def wire_name_for(name, lookup_name)
317
+ case name
318
+ when String then name
319
+ when Symbol then lookup_name
320
+ else raise HeaderError, "HTTP header must be a String or Symbol: #{name.inspect}"
321
+ end
222
322
  end
223
323
 
324
+ # Transforms name to canonical HTTP header capitalization
325
+ #
326
+ # @return [String]
327
+ # @api private
328
+ def normalize_header(name) = self.class.normalizer.call(name)
329
+
224
330
  # Ensures there is no new line character in the header value
225
331
  #
226
332
  # @param [String] value
227
333
  # @raise [HeaderError] if value includes new line character
228
334
  # @return [String] stringified header value
335
+ # @api private
229
336
  def validate_value(value)
230
337
  v = value.to_s
231
- return v unless v.include?("\n")
338
+ return v unless v.include?("\n") || v.include?("\r")
232
339
 
233
340
  raise HeaderError, "Invalid HTTP header field value: #{v.inspect}"
234
341
  end
@@ -11,18 +11,33 @@ module HTTP
11
11
 
12
12
  class << self
13
13
  extend Forwardable
14
- def_delegators :instance, :encode, :decode
14
+
15
+ def_delegators :instance, :encode, :decode # steep:ignore
16
+ end
17
+
18
+ # Encodes data into the MIME type format
19
+ #
20
+ # @example
21
+ # adapter.encode("foo" => "bar")
22
+ #
23
+ # @return [String] encoded representation
24
+ # @raise [Error] if not implemented by subclass
25
+ # @api public
26
+ def encode(*)
27
+ raise Error, "#{self.class} does not supports #encode"
15
28
  end
16
29
 
17
- # rubocop:disable Style/DocumentDynamicEvalDefinition
18
- %w[encode decode].each do |operation|
19
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
20
- def #{operation}(*)
21
- fail Error, "\#{self.class} does not supports ##{operation}"
22
- end
23
- RUBY
30
+ # Decodes data from the MIME type format
31
+ #
32
+ # @example
33
+ # adapter.decode("{\"foo\":\"bar\"}")
34
+ #
35
+ # @return [Object] decoded data
36
+ # @raise [Error] if not implemented by subclass
37
+ # @api public
38
+ def decode(*)
39
+ raise Error, "#{self.class} does not supports #decode"
24
40
  end
25
- # rubocop:enable Style/DocumentDynamicEvalDefinition
26
41
  end
27
42
  end
28
43
  end
@@ -4,17 +4,32 @@ require "json"
4
4
  require "http/mime_type/adapter"
5
5
 
6
6
  module HTTP
7
+ # MIME type registry and adapter interface
7
8
  module MimeType
8
9
  # JSON encode/decode MIME type adapter
9
10
  class JSON < Adapter
10
11
  # Encodes object to JSON
12
+ #
13
+ # @example
14
+ # adapter = HTTP::MimeType::JSON.new
15
+ # adapter.encode(foo: "bar")
16
+ #
17
+ # @param [Object] obj object to encode
18
+ # @api public
19
+ # @return [String]
11
20
  def encode(obj)
12
- return obj.to_json if obj.respond_to?(:to_json)
13
-
14
- ::JSON.dump obj
21
+ obj.to_json
15
22
  end
16
23
 
17
- # Decodes JSON
24
+ # Decodes JSON string into Ruby object
25
+ #
26
+ # @example
27
+ # adapter = HTTP::MimeType::JSON.new
28
+ # adapter.decode('{"foo":"bar"}')
29
+ #
30
+ # @param [String] str JSON string to decode
31
+ # @api public
32
+ # @return [Object]
18
33
  def decode(str)
19
34
  ::JSON.parse str
20
35
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "http/errors"
4
+
3
5
  module HTTP
4
6
  # MIME type encode/decode adapters
5
7
  module MimeType
@@ -24,6 +26,7 @@ module HTTP
24
26
  #
25
27
  # @param [#to_s] type
26
28
  # @param [#encode, #decode] adapter
29
+ # @api public
27
30
  # @return [void]
28
31
  def register_adapter(type, adapter)
29
32
  adapters[type.to_s] = adapter
@@ -31,11 +34,15 @@ module HTTP
31
34
 
32
35
  # Returns adapter associated with MIME type
33
36
  #
37
+ # @example
38
+ # HTTP::MimeType["application/json"]
39
+ #
34
40
  # @param [#to_s] type
35
41
  # @raise [Error] if no adapter found
42
+ # @api public
36
43
  # @return [Class]
37
44
  def [](type)
38
- adapters[normalize type] || raise(Error, "Unknown MIME type: #{type}")
45
+ adapters[normalize type] || raise(UnsupportedMimeTypeError, "Unknown MIME type: #{type}")
39
46
  end
40
47
 
41
48
  # Register a shortcut for MIME type
@@ -46,6 +53,7 @@ module HTTP
46
53
  #
47
54
  # @param [#to_s] type
48
55
  # @param [#to_sym] shortcut
56
+ # @api public
49
57
  # @return [void]
50
58
  def register_alias(type, shortcut)
51
59
  aliases[shortcut.to_sym] = type.to_s
@@ -53,7 +61,11 @@ module HTTP
53
61
 
54
62
  # Resolves type by shortcut if possible
55
63
  #
64
+ # @example
65
+ # HTTP::MimeType.normalize(:json)
66
+ #
56
67
  # @param [#to_s] type
68
+ # @api public
57
69
  # @return [String]
58
70
  def normalize(type)
59
71
  aliases.fetch type, type.to_s
@@ -61,12 +73,18 @@ module HTTP
61
73
 
62
74
  private
63
75
 
64
- # :nodoc:
76
+ # Returns the adapters registry hash
77
+ #
78
+ # @api private
79
+ # @return [Hash]
65
80
  def adapters
66
81
  @adapters ||= {}
67
82
  end
68
83
 
69
- # :nodoc:
84
+ # Returns the aliases registry hash
85
+ #
86
+ # @api private
87
+ # @return [Hash]
70
88
  def aliases
71
89
  @aliases ||= {}
72
90
  end