Feature #20164
openAdd Exception#deconstruct_keys
Description
It would be convenient to perform pattern matching with exception classes. So Exception#deconstruct_keys should return a hash with :message (original_message) as well as any other keys specific to the exception subclass.
Examples:
begin #code rescue => err case err in StandardError(message: /Permission denied/) abort "please select a different file" in NameError(name: :foo) retry if require "foo_helper else raise end end
Updated by palkan (Vladimir Dementyev) almost 2 years ago
+1 for making exceptions pattern-matching friendly. The example above demonstrates the use case pretty well (class + message matching).
The question is what keys must be supported for each standard exception class? The plain Ruby implementation could be as follows:
class Exception # Not sure if we need to take into account the `keys` argument def deconstruct_keys(*) = {message:, cause:} end class NameError def deconstruct_keys(*) = super.merge(name:) end
Updated by Dan0042 (Daniel DeLorme) almost 2 years ago
palkan (Vladimir Dementyev) wrote in #note-1:
The question is what keys must be supported for each standard exception class?
I computed a quick list and it would look something like this.
Exception [:backtrace, :backtrace_locations, :cause, :message] StopIteration [:result] FrozenError [:receiver] SignalException [:signo, :signm] KeyError [:receiver, :key] LoadError [:path] LocalJumpError [:exit_value, :reason] NameError [:name, :receiver, :local_variables] NoMatchingPatternKeyError [:key, :matchee] NoMethodError [:args, :private_call?] SyntaxError [:path] SystemCallError [:errno] SystemExit [:status, :success?] UncaughtThrowError [:tag, :value] Encoding::InvalidByteSequenceError [:readagain_bytes, :source_encoding, :source_encoding_name, :incomplete_input?, :destination_encoding, :destination_encoding_name, :error_bytes] Encoding::UndefinedConversionError [:error_char, :source_encoding, :source_encoding_name, :destination_encoding, :destination_encoding_name] OptionParser::ParseError [:args, :reason, :additional] Ractor::RemoteError [:ractor] Timeout::Error [:thread] RDoc::RI::Driver::NotFoundError [:name] RDoc::Store::MissingFileError [:name, :file, :store] Reline::Config::InvalidInputrc [:file, :lineno] Gem::LoadError [:name, :requirement] Gem::ConflictError [:target, :conflicts] Gem::DependencyResolutionError [:conflict, :conflicting_dependencies] Gem::FilePermissionError [:directory] Gem::FormatException [:file_path] Gem::GemNotInHomeException [:spec] Gem::ImpossibleDependenciesError [:request, :build_message, :dependency, :conflicts] Gem::MissingSpecVersionError [:specs] Gem::RuntimeRequirementNotMetError [:suggestion] Gem::SpecificGemNotFoundException [:name, :errors, :version] Gem::SystemExitException [:exit_code] Gem::UninstallError [:spec] Gem::UnknownCommandError [:unknown_command] Gem::UnsatisfiableDependencyError [:name, :dependency, :errors, :version] Gem::RequestSet::Lockfile::ParseError [:column, :path, :line] Gem::Resolver::Molinillo::CircularDependencyError [:dependencies] Gem::Resolver::Molinillo::NoSuchDependencyError [:dependency, :required_by] Gem::Resolver::Molinillo::VersionConflict [:specification_provider, :conflicts] Even assuming some of those keys are not relevant, that's still a lot of #deconstruct_keys to define for various classes, so maybe a dynamic approach like below would be more feasible?
class Exception def deconstruct_keys(*keys) h = @diagnostics ||= {} keys.each do |k| unless h.key?(k) h[a] = self.send(k) rescue nil end end h end end