http-2-next 0.2.4 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0951e3da74f70138e1ce72ce1669f372a265c83e57367b97919a1f8dd1a4788f'
4
- data.tar.gz: afd99005840bc669dcc4d3ff7e8dec2779d9feff4bcf50865364119b1a492646
3
+ metadata.gz: 8fa01852fa00c35563fb999f007c1894c4b46a18a40d311d21908c42fc1b17f1
4
+ data.tar.gz: 7366509fdd22062f761abc7c4c6c46fa2e707ba953c17e3e86f449a67b5c38f1
5
5
  SHA512:
6
- metadata.gz: 97af505781e2963e4b5717ec5f2ddc334bcbef5a7ee0e4f7433c171ac917096a1694c0c51aaf2a888e50da406e0692f4f57a52be27f625533513fcf777937b3d
7
- data.tar.gz: '08d581534ac39f3b18a268f2f727602635bb376bb2335ba997159658880c3f3c00b87ad2767b928d1468d3bc44d7bee49c24583d97662e5b5761b94a42d9bdd8'
6
+ metadata.gz: a8e6170d679b518d9de1fb1222d8a4c4996906e4a015a67d7418bb5c56af7c60e7551206303518612b16f19eddaaf1d50af96eb75d2611c7e4709a3f4401212c
7
+ data.tar.gz: 75c0652bc0b55feb14883841aa1119f4c0751ad5b435bd7e2de3443f8c6a354eaae25777c71c6fb1d9e35e90760af34c7a3319e418c5ccf5f44909f0741910c1
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/http-2-next.svg)](http://rubygems.org/gems/http-2-next)
4
4
  [![Build status](https://gitlab.com/honeyryderchuck/http-2-next/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/http-2-next/commits/master)
5
- [![coverage report](https://gitlab.com/honeyryderchuck/http-2-next/badges/master/coverage.svg)](https://honeyryderchuck.gitlab.io/http-2-next/coverage/#_AllFiles)
5
+ [![coverage report](https://gitlab.com/honeyryderchuck/http-2-next/badges/master/coverage.svg?job=coverage)](https://honeyryderchuck.gitlab.io/http-2-next/coverage/#_AllFiles)
6
6
 
7
7
  **Attention!** This is a fork of the [http-2](https://github.com/igrigorik/http-2) gem.
8
8
 
data/lib/http/2/next.rb CHANGED
@@ -4,11 +4,8 @@ require "http/2/next/version"
4
4
  require "http/2/next/extensions"
5
5
  require "http/2/next/error"
6
6
  require "http/2/next/emitter"
7
- require "http/2/next/buffer"
8
7
  require "http/2/next/flow_buffer"
9
- require "http/2/next/huffman"
10
- require "http/2/next/huffman_statemachine"
11
- require "http/2/next/compressor"
8
+ require "http/2/next/header"
12
9
  require "http/2/next/framer"
13
10
  require "http/2/next/connection"
14
11
  require "http/2/next/client"
@@ -51,18 +51,19 @@ module HTTP2Next
51
51
  include Emitter
52
52
  include Error
53
53
 
54
+ using StringExtensions
55
+
54
56
  # Connection state (:new, :closed).
55
57
  attr_reader :state
56
58
 
57
59
  # Size of current connection flow control window (by default, set to
58
60
  # infinity, but is automatically updated on receipt of peer settings).
59
61
  attr_reader :local_window
60
- attr_reader :remote_window
62
+ attr_reader :remote_window, :remote_settings
61
63
  alias window local_window
62
64
 
63
65
  # Current settings value for local and peer
64
66
  attr_reader :local_settings
65
- attr_reader :remote_settings
66
67
 
67
68
  # Pending settings value
68
69
  # Sent but not ack'ed settings
@@ -72,6 +73,9 @@ module HTTP2Next
72
73
  # are not counted towards the stream limit).
73
74
  attr_reader :active_stream_count
74
75
 
76
+ # Max number of streams that can be in-transit in this connection.
77
+ attr_writer :max_streams
78
+
75
79
  # Initializes new connection object.
76
80
  #
77
81
  def initialize(settings = {})
@@ -82,6 +86,7 @@ module HTTP2Next
82
86
  @decompressor = Header::Decompressor.new(settings)
83
87
 
84
88
  @active_stream_count = 0
89
+ @max_streams = nil
85
90
  @last_activated_stream = 0
86
91
  @last_stream_id = 0
87
92
  @streams = {}
@@ -95,12 +100,13 @@ module HTTP2Next
95
100
  @remote_window_limit = @remote_settings[:settings_initial_window_size]
96
101
  @remote_window = @remote_window_limit
97
102
 
98
- @recv_buffer = Buffer.new
103
+ @recv_buffer = "".b
99
104
  @continuation = []
100
105
  @error = nil
101
106
 
102
107
  @h2c_upgrade = nil
103
108
  @closed_since = nil
109
+ @received_frame = false
104
110
  end
105
111
 
106
112
  def closed?
@@ -114,7 +120,7 @@ module HTTP2Next
114
120
  # @param parent [Stream]
115
121
  def new_stream(**args)
116
122
  raise ConnectionClosed if @state == :closed
117
- raise StreamLimitExceeded if @active_stream_count >= @remote_settings[:settings_max_concurrent_streams]
123
+ raise StreamLimitExceeded if @active_stream_count >= (@max_streams || @remote_settings[:settings_max_concurrent_streams])
118
124
 
119
125
  connection_error(:protocol_error, msg: "id is smaller than previous") if @stream_id < @last_activated_stream
120
126
 
@@ -155,7 +161,7 @@ module HTTP2Next
155
161
  send(type: :goaway, last_stream: last_stream,
156
162
  error: error, payload: payload)
157
163
  @state = :closed
158
- @closed_since = Time.now
164
+ @closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
159
165
  end
160
166
 
161
167
  # Sends a WINDOW_UPDATE frame to the peer.
@@ -172,7 +178,7 @@ module HTTP2Next
172
178
  # @param settings [Array or Hash]
173
179
  def settings(payload)
174
180
  payload = payload.to_a
175
- connection_error if validate_settings(@local_role, payload)
181
+ validate_settings(@local_role, payload)
176
182
  @pending_settings << payload
177
183
  send(type: :settings, stream: 0, payload: payload)
178
184
  @pending_settings << payload
@@ -209,6 +215,11 @@ module HTTP2Next
209
215
  end
210
216
 
211
217
  while (frame = @framer.parse(@recv_buffer))
218
+ if is_a?(Client) && !@received_frame
219
+ connection_error(:protocol_error, msg: "didn't receive settings") if frame[:type] != :settings
220
+ @received_frame = true
221
+ end
222
+
212
223
  # Implementations MUST discard frames
213
224
  # that have unknown or unsupported types.
214
225
  if frame[:type].nil?
@@ -236,7 +247,7 @@ module HTTP2Next
236
247
  @continuation.clear
237
248
 
238
249
  frame.delete(:length)
239
- frame[:payload] = Buffer.new(payload)
250
+ frame[:payload] = payload
240
251
  frame[:flags] << :end_headers
241
252
  end
242
253
 
@@ -469,7 +480,7 @@ module HTTP2Next
469
480
  # the connection, although a new connection can be established
470
481
  # for new streams.
471
482
  @state = :closed
472
- @closed_since = Time.now
483
+ @closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
473
484
  emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
474
485
  when :altsvc
475
486
  # 4. The ALTSVC HTTP/2 Frame
@@ -494,7 +505,7 @@ module HTTP2Next
494
505
  when :ping
495
506
  ping_management(frame)
496
507
  else
497
- connection_error if (Time.now - @closed_since) > 15
508
+ connection_error if (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @closed_since) > 15
498
509
  end
499
510
  else
500
511
  connection_error
@@ -517,8 +528,6 @@ module HTTP2Next
517
528
  def validate_settings(role, settings)
518
529
  settings.each do |key, v|
519
530
  case key
520
- when :settings_header_table_size
521
- # Any value is valid
522
531
  when :settings_enable_push
523
532
  case role
524
533
  when :server
@@ -526,32 +535,41 @@ module HTTP2Next
526
535
  # Clients MUST reject any attempt to change the
527
536
  # SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
528
537
  # message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
529
- return ProtocolError.new("invalid #{key} value") unless v.zero?
538
+ next if v.zero?
539
+
540
+ connection_error(:protocol_error, msg: "invalid #{key} value")
530
541
  when :client
531
542
  # Any value other than 0 or 1 MUST be treated as a
532
543
  # connection error (Section 5.4.1) of type PROTOCOL_ERROR.
533
- return ProtocolError.new("invalid #{key} value") unless v.zero? || v == 1
544
+ next if v.zero? || v == 1
545
+
546
+ connection_error(:protocol_error, msg: "invalid #{key} value")
534
547
  end
535
- when :settings_max_concurrent_streams
536
- # Any value is valid
537
548
  when :settings_initial_window_size
538
549
  # Values above the maximum flow control window size of 2^31-1 MUST
539
550
  # be treated as a connection error (Section 5.4.1) of type
540
551
  # FLOW_CONTROL_ERROR.
541
- return FlowControlError.new("invalid #{key} value") unless v <= 0x7fffffff
552
+ next if v <= 0x7fffffff
553
+
554
+ connection_error(:flow_control_error, msg: "invalid #{key} value")
542
555
  when :settings_max_frame_size
543
556
  # The initial value is 2^14 (16,384) octets. The value advertised
544
557
  # by an endpoint MUST be between this initial value and the maximum
545
558
  # allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
546
559
  # Values outside this range MUST be treated as a connection error
547
560
  # (Section 5.4.1) of type PROTOCOL_ERROR.
548
- return ProtocolError.new("invalid #{key} value") unless v >= 16_384 && v <= 16_777_215
549
- when :settings_max_header_list_size
561
+ next if v >= 16_384 && v <= 16_777_215
562
+
563
+ connection_error(:protocol_error, msg: "invalid #{key} value")
564
+ # when :settings_max_concurrent_streams
565
+ # Any value is valid
566
+ # when :settings_header_table_size
567
+ # Any value is valid
568
+ # when :settings_max_header_list_size
550
569
  # Any value is valid
551
570
  # else # ignore unknown settings
552
571
  end
553
572
  end
554
- nil
555
573
  end
556
574
 
557
575
  # Update connection settings based on parameters set by the peer.
@@ -568,8 +586,7 @@ module HTTP2Next
568
586
  # Process pending settings we have sent.
569
587
  [@pending_settings.shift, :local]
570
588
  else
571
- check = validate_settings(@remote_role, frame[:payload])
572
- connection_error(check) if check
589
+ validate_settings(@remote_role, frame[:payload])
573
590
  [frame[:payload], :remote]
574
591
  end
575
592
 
@@ -656,7 +673,7 @@ module HTTP2Next
656
673
  #
657
674
  # @param frame [Hash]
658
675
  def decode_headers(frame)
659
- frame[:payload] = @decompressor.decode(frame[:payload], frame) if frame[:payload].is_a? Buffer
676
+ frame[:payload] = @decompressor.decode(frame[:payload], frame) if frame[:payload].is_a?(String)
660
677
  rescue CompressionError => e
661
678
  connection_error(:compression_error, e: e)
662
679
  rescue ProtocolError => e
@@ -671,15 +688,16 @@ module HTTP2Next
671
688
  # @return [Array of Frame]
672
689
  def encode_headers(frame)
673
690
  payload = frame[:payload]
674
- payload = @compressor.encode(payload) unless payload.is_a? Buffer
691
+ payload = @compressor.encode(payload) unless payload.is_a?(String)
675
692
 
676
693
  frames = []
677
694
 
678
- while payload.bytesize > 0
695
+ while payload && payload.bytesize > 0
679
696
  cont = frame.dup
680
697
  cont[:type] = :continuation
681
698
  cont[:flags] = []
682
- cont[:payload] = payload.slice!(0, @remote_settings[:settings_max_frame_size])
699
+ cont[:payload] = payload.byteslice(0, @remote_settings[:settings_max_frame_size])
700
+ payload = payload.byteslice(@remote_settings[:settings_max_frame_size]..-1)
683
701
  frames << cont
684
702
  end
685
703
  if frames.empty?
@@ -703,10 +721,10 @@ module HTTP2Next
703
721
  # @param priority [Integer]
704
722
  # @param window [Integer]
705
723
  # @param parent [Stream]
706
- def activate_stream(id: nil, **args)
724
+ def activate_stream(id:, **args)
707
725
  connection_error(msg: "Stream ID already exists") if @streams.key?(id)
708
726
 
709
- raise StreamLimitExceeded if @active_stream_count >= @local_settings[:settings_max_concurrent_streams]
727
+ raise StreamLimitExceeded if @active_stream_count >= (@max_streams || @local_settings[:settings_max_concurrent_streams])
710
728
 
711
729
  stream = Stream.new(connection: self, id: id, **args)
712
730
 
@@ -719,12 +737,12 @@ module HTTP2Next
719
737
  # to any in-flight frames while close is registered on both sides.
720
738
  # References to such streams will be purged whenever another stream
721
739
  # is closed, with a minimum of 15s RTT time window.
722
- @streams_recently_closed.reject do |_, v|
723
- to_delete = (Time.now - v) > 15
740
+ @streams_recently_closed.reject! do |stream_id, v|
741
+ to_delete = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - v) > 15
724
742
  @streams.delete stream_id if to_delete
725
743
  to_delete
726
744
  end
727
- @streams_recently_closed[id] = Time.now
745
+ @streams_recently_closed[id] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
728
746
  end
729
747
 
730
748
  stream.on(:promise, &method(:promise)) if is_a? Server
@@ -742,7 +760,7 @@ module HTTP2Next
742
760
 
743
761
  def verify_pseudo_headers(frame, mandatory_headers)
744
762
  headers = frame[:payload]
745
- return if headers.is_a?(Buffer)
763
+ return if headers.is_a?(String)
746
764
 
747
765
  pseudo_headers = headers.take_while do |field, value|
748
766
  # use this loop to validate pseudo-headers
@@ -9,19 +9,18 @@ module HTTP2Next
9
9
  #
10
10
  # @param event [Symbol]
11
11
  # @param block [Proc] callback function
12
- def add_listener(event, &block)
12
+ def on(event, &block)
13
13
  raise ArgumentError, "must provide callback" unless block_given?
14
14
 
15
15
  listeners(event.to_sym).push block
16
16
  end
17
- alias on add_listener
18
17
 
19
18
  # Subscribe to next event (at most once) for specified type.
20
19
  #
21
20
  # @param event [Symbol]
22
21
  # @param block [Proc] callback function
23
22
  def once(event, &block)
24
- add_listener(event) do |*args, &callback|
23
+ on(event) do |*args, &callback|
25
24
  block.call(*args, &callback)
26
25
  :delete
27
26
  end
@@ -34,7 +33,7 @@ module HTTP2Next
34
33
  # @param block [Proc] callback function
35
34
  def emit(event, *args, &block)
36
35
  listeners(event).delete_if do |cb|
37
- cb.call(*args, &block) == :delete
36
+ :delete == cb.call(*args, &block) # rubocop:disable Style/YodaCondition
38
37
  end
39
38
  end
40
39
 
@@ -10,6 +10,7 @@ module HTTP2Next
10
10
 
11
11
  class Error < StandardError
12
12
  def self.inherited(klass)
13
+ super
13
14
  type = klass.name.split("::").last
14
15
  type = type.gsub(/([^\^])([A-Z])/, '\1_\2').downcase.to_sym
15
16
  HTTP2Next::Error.types[type] = klass
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP2Next
4
- module Extensions
4
+ module RegexpExtensions
5
5
  unless Regexp.method_defined?(:match?)
6
6
  refine Regexp do
7
7
  def match?(*args)
@@ -10,4 +10,29 @@ module HTTP2Next
10
10
  end
11
11
  end
12
12
  end
13
+
14
+ module StringExtensions
15
+ refine String do
16
+ def read(n)
17
+ chunk = byteslice(0..n - 1)
18
+ remaining = byteslice(n..-1)
19
+ remaining ? replace(remaining) : clear
20
+ chunk
21
+ end
22
+
23
+ def read_uint32
24
+ read(4).unpack1("N")
25
+ end
26
+
27
+ def shift_byte
28
+ read(1).ord
29
+ end
30
+
31
+ unless String.method_defined?(:unpack1)
32
+ def unpack1(format)
33
+ unpack(format).first
34
+ end
35
+ end
36
+ end
37
+ end
13
38
  end
@@ -73,6 +73,7 @@ module HTTP2Next
73
73
 
74
74
  while (frame = send_buffer.retrieve(@remote_window))
75
75
 
76
+ # puts "#{self.class} -> #{@remote_window}"
76
77
  sent = frame[:payload].bytesize
77
78
 
78
79
  manage_state(frame) do
@@ -137,10 +138,13 @@ module HTTP2Next
137
138
 
138
139
  # Split frame so that it fits in the window
139
140
  # TODO: consider padding!
140
- frame[:payload] = payload.slice!(0, window_size)
141
- frame[:length] = frame[:payload].bytesize
142
- chunk[:length] = payload.bytesize
141
+ frame_bytes = payload.byteslice(0, window_size)
142
+ payload = payload.byteslice(window_size..-1)
143
+
144
+ frame[:payload] = frame_bytes
145
+ frame[:length] = frame_bytes.bytesize
143
146
  chunk[:payload] = payload
147
+ chunk[:length] = payload.bytesize
144
148
 
145
149
  # if no longer last frame in sequence...
146
150
  frame[:flags] -= [:end_stream] if end_stream
@@ -4,6 +4,8 @@ module HTTP2Next
4
4
  # Performs encoding, decoding, and validation of binary HTTP/2 frames.
5
5
  #
6
6
  class Framer
7
+ using StringExtensions
8
+
7
9
  include Error
8
10
 
9
11
  # Default value of max frame size (16384 bytes)
@@ -154,9 +156,10 @@ module HTTP2Next
154
156
  # Decodes common 9-byte header.
155
157
  #
156
158
  # @param buf [Buffer]
159
+ # @return [Hash] the corresponding frame
157
160
  def read_common_header(buf)
158
161
  frame = {}
159
- len_hi, len_lo, type, flags, stream = buf.slice(0, 9).unpack(HEADERPACK)
162
+ len_hi, len_lo, type, flags, stream = buf.byteslice(0, 9).unpack(HEADERPACK)
160
163
 
161
164
  frame[:length] = (len_hi << FRAME_LENGTH_HISHIFT) | len_lo
162
165
  frame[:type], = FRAME_TYPES.find { |_t, pos| type == pos }
@@ -175,7 +178,7 @@ module HTTP2Next
175
178
  #
176
179
  # @param frame [Hash]
177
180
  def generate(frame)
178
- bytes = Buffer.new
181
+ bytes = "".b
179
182
  length = 0
180
183
 
181
184
  frame[:flags] ||= []
@@ -221,7 +224,7 @@ module HTTP2Next
221
224
  raise CompressionError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
222
225
 
223
226
  frame[:payload].each do |(k, v)|
224
- if k.is_a? Integer
227
+ if k.is_a? Integer # rubocop:disable Style/GuardClause
225
228
  DEFINED_SETTINGS.value?(k) || next
226
229
  else
227
230
  k = DEFINED_SETTINGS[k]
@@ -333,10 +336,10 @@ module HTTP2Next
333
336
  #
334
337
  # @param buf [Buffer]
335
338
  def parse(buf)
336
- return nil if buf.size < 9
339
+ return if buf.size < 9
337
340
 
338
341
  frame = read_common_header(buf)
339
- return nil if buf.size < 9 + frame[:length]
342
+ return if buf.size < 9 + frame[:length]
340
343
 
341
344
  raise ProtocolError, "payload too large" if frame[:length] > @local_max_frame_size
342
345
 
@@ -353,11 +356,11 @@ module HTTP2Next
353
356
  if FRAME_TYPES_WITH_PADDING.include?(frame[:type])
354
357
  padded = frame[:flags].include?(:padded)
355
358
  if padded
356
- padlen = payload.read(1).unpack(UINT8).first
359
+ padlen = payload.read(1).unpack1(UINT8)
357
360
  frame[:padding] = padlen + 1
358
361
  raise ProtocolError, "padding too long" if padlen > payload.bytesize
359
362
 
360
- payload.slice!(-padlen, padlen) if padlen > 0
363
+ payload = payload.byteslice(0, payload.bytesize - padlen) if padlen > 0
361
364
  frame[:length] -= frame[:padding]
362
365
  frame[:flags].delete(:padded)
363
366
  end
@@ -371,7 +374,9 @@ module HTTP2Next
371
374
  e_sd = payload.read_uint32
372
375
  frame[:dependency] = e_sd & RBIT
373
376
  frame[:exclusive] = (e_sd & EBIT) != 0
374
- frame[:weight] = payload.getbyte + 1
377
+ weight = payload.byteslice(0, 1).ord + 1
378
+ frame[:weight] = weight
379
+ payload = payload.byteslice(1..-1)
375
380
  end
376
381
  frame[:payload] = payload.read(frame[:length])
377
382
  when :priority
@@ -380,7 +385,9 @@ module HTTP2Next
380
385
  e_sd = payload.read_uint32
381
386
  frame[:dependency] = e_sd & RBIT
382
387
  frame[:exclusive] = (e_sd & EBIT) != 0
383
- frame[:weight] = payload.getbyte + 1
388
+ weight = payload.byteslice(0, 1).ord + 1
389
+ frame[:weight] = weight
390
+ payload = payload.byteslice(1..-1)
384
391
  when :rst_stream
385
392
  raise FrameSizeError, "Invalid length for RST_STREAM (#{frame[:length]} != 4)" if frame[:length] != 4
386
393
 
@@ -395,7 +402,7 @@ module HTTP2Next
395
402
  raise ProtocolError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
396
403
 
397
404
  (frame[:length] / 6).times do
398
- id = payload.read(2).unpack(UINT16).first
405
+ id = payload.read(2).unpack1(UINT16)
399
406
  val = payload.read_uint32
400
407
 
401
408
  # Unsupported or unrecognized settings MUST be ignored.
@@ -425,10 +432,12 @@ module HTTP2Next
425
432
  when :altsvc
426
433
  frame[:max_age], frame[:port] = payload.read(6).unpack(UINT32 + UINT16)
427
434
 
428
- len = payload.getbyte
435
+ len = payload.byteslice(0, 1).ord
436
+ payload = payload.byteslice(1..-1)
429
437
  frame[:proto] = payload.read(len) if len > 0
430
438
 
431
- len = payload.getbyte
439
+ len = payload.byteslice(0, 1).ord
440
+ payload = payload.byteslice(1..-1)
432
441
  frame[:host] = payload.read(len) if len > 0
433
442
 
434
443
  frame[:origin] = payload.read(payload.size) unless payload.empty?
@@ -437,7 +446,7 @@ module HTTP2Next
437
446
  origins = []
438
447
 
439
448
  until payload.empty?
440
- len = payload.read(2).unpack(UINT16).first
449
+ len = payload.read(2).unpack1(UINT16)
441
450
  origins << payload.read(len)
442
451
  end
443
452
 
@@ -452,17 +461,16 @@ module HTTP2Next
452
461
 
453
462
  def pack_error(e)
454
463
  unless e.is_a? Integer
455
- raise CompressionError, "Unknown error ID for #{e}" if DEFINED_ERRORS[e].nil?
456
-
457
464
  e = DEFINED_ERRORS[e]
465
+
466
+ raise CompressionError, "Unknown error ID for #{e}" unless e
458
467
  end
459
468
 
460
469
  [e].pack(UINT32)
461
470
  end
462
471
 
463
472
  def unpack_error(error)
464
- name, = DEFINED_ERRORS.find { |_name, v| v == error }
465
- name || error
473
+ DEFINED_ERRORS.key(error) || error
466
474
  end
467
475
  end
468
476
  end