Skip to content

Commit 4cb320f

Browse files
committed
🥅 Add ResponseParseError#parser_methods
This also updates `ResponseParseError#==` to returns true when all attributes are equal, except for `#backtrace` and `#backtrace_locations` which are replaced with `#parser_methods`. This allows deserialized errors to be compared.
1 parent d32b8bc commit 4cb320f

File tree

1 file changed

+47
-10
lines changed

1 file changed

+47
-10
lines changed

lib/net/imap/errors.rb

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -172,18 +172,11 @@ def detailed_message(parser_state: Net::IMAP.debug,
172172
]
173173
end
174174
if parser_backtrace
175-
backtrace_locations&.each_with_index do |loc, idx|
176-
next if loc.base_label.include? "parse_error"
177-
break if loc.base_label == "parse"
178-
if loc.label.include?("#") # => Class#method, since ruby 3.4
179-
next unless loc.label&.include?(parser_class.name)
180-
else
181-
next unless loc.path&.include?("net/imap/response_parser")
182-
end
175+
normalized_parser_backtrace.each do |idx, path, lineno, label, base_label|
183176
msg << "\n %s: %s (%s:%d)" % [
184177
hl["%{key}caller[%{/key}%{idx}%%2d%{/idx}%{key}]%{/key}"] % idx,
185-
hl["%{label}%%-30s%{/label}"] % loc.base_label,
186-
File.basename(loc.path, ".rb"), loc.lineno
178+
hl["%{label}%%-30s%{/label}"] % base_label,
179+
File.basename(path, ".rb"), lineno
187180
]
188181
end
189182
end
@@ -198,12 +191,56 @@ def detailed_message(parser_state: Net::IMAP.debug,
198191
def processed_string = string && pos && string[...pos]
199192
def remaining_string = string && pos && string[pos..]
200193

194+
# Returns true when all attributes are equal, except for #backtrace and
195+
# #backtrace_locations which are replaced with #parser_methods. This
196+
# allows deserialized errors to be compared.
197+
def ==(other)
198+
return false if self.class != other.class
199+
methods = parser_methods
200+
other_methods = other.parser_methods
201+
message == other.message &&
202+
methods == other_methods &&
203+
string == other.string &&
204+
pos == other.pos &&
205+
lex_state == other.lex_state &&
206+
token == other.token
207+
end
208+
209+
# Lists the methods (from #backtrace_locations or #backtrace) called on
210+
# parser_class (since ruby 3.4) or which have "net/imap/response_parser"
211+
# in the path (before ruby 3.4). Most parser method names are based on
212+
# rules in the IMAP grammar.
213+
def parser_methods = normalized_parser_backtrace.map(&:last)
214+
201215
private
202216

217+
def normalized_parser_backtrace
218+
normalize_backtrace
219+
.take_while {|_, _, _, _, base_label| base_label != "parse" }
220+
.reject {|_, _, _, _, base_label| base_label.nil? }
221+
.reject {|_, _, _, _, base_label| base_label.include? "parse_error" }
222+
.select {|_, path, _, label, _|
223+
if label.include?("#") # => Class#method, since ruby 3.4
224+
label.include?(parser_class.name)
225+
else
226+
path.include?("net/imap/response_parser")
227+
end
228+
}
229+
end
230+
231+
def normalize_backtrace
232+
(backtrace_locations&.each_with_index&.map {|loc, idx|
233+
[idx, loc.path, loc.lineno, loc.label, loc.base_label]
234+
} || backtrace&.each_with_index&.map {|bt, idx|
235+
[idx, *bt.match(/\A(\S+):(\d+):in [`'](.*?([\w]+[?!]?))'\z/)&.captures]
236+
} || [])
237+
end
238+
203239
def default_highlight_from_env
204240
(ENV["FORCE_COLOR"] || "") !~ /\A(?:0|)\z/ ||
205241
(ENV["TERM"] || "") !~ /\A(?:dumb|unknown|)\z/i
206242
end
243+
207244
end
208245

209246
# Superclass of all errors used to encapsulate "fail" responses

0 commit comments

Comments
 (0)