ruby/lib/bundler/errors.rb
David Rodríguez 89eba5074e [rubygems/rubygems] Only raise DSLError during Gemfile parsing when it's actually useful
DSLError prints the specific line in a Gemfile where the error was
raised. That's helpful when the error was explicitly raised by the
Gemfile DSL or, in the case it's implicitly raised, when the offending
code lives right in the Gemfile.

If it's an internal error, or something buried dowm in user code called
from the Gemfile, `DSLError` is not helpful since it hides the actual
culprit.

This commit tries to only raise `DSLError` in the cases mentioned above
and otherwise let the original error be raised.

b30ff5a682
2024-09-11 11:28:12 +00:00

249 lines
8 KiB
Ruby

# frozen_string_literal: true
module Bundler
class BundlerError < StandardError
def self.status_code(code)
define_method(:status_code) { code }
if match = BundlerError.all_errors.find {|_k, v| v == code }
error, _ = match
raise ArgumentError,
"Trying to register #{self} for status code #{code} but #{error} is already registered"
end
BundlerError.all_errors[self] = code
end
def self.all_errors
@all_errors ||= {}
end
end
class GemfileError < BundlerError; status_code(4); end
class InstallError < BundlerError; status_code(5); end
# Internal error, should be rescued
class SolveFailure < BundlerError; status_code(6); end
class GemNotFound < BundlerError; status_code(7); end
class InstallHookError < BundlerError; status_code(8); end
class GemfileNotFound < BundlerError; status_code(10); end
class GitError < BundlerError; status_code(11); end
class DeprecatedError < BundlerError; status_code(12); end
class PathError < BundlerError; status_code(13); end
class GemspecError < BundlerError; status_code(14); end
class InvalidOption < BundlerError; status_code(15); end
class ProductionError < BundlerError; status_code(16); end
class HTTPError < BundlerError
status_code(17)
def filter_uri(uri)
URICredentialsFilter.credential_filtered_uri(uri)
end
end
class RubyVersionMismatch < BundlerError; status_code(18); end
class SecurityError < BundlerError; status_code(19); end
class LockfileError < BundlerError; status_code(20); end
class CyclicDependencyError < BundlerError; status_code(21); end
class GemfileLockNotFound < BundlerError; status_code(22); end
class PluginError < BundlerError; status_code(29); end
class ThreadCreationError < BundlerError; status_code(33); end
class APIResponseMismatchError < BundlerError; status_code(34); end
class APIResponseInvalidDependenciesError < BundlerError; status_code(35); end
class GemfileEvalError < GemfileError; end
class MarshalError < StandardError; end
class ChecksumMismatchError < SecurityError
def initialize(lock_name, existing, checksum)
@lock_name = lock_name
@existing = existing
@checksum = checksum
end
def message
<<~MESSAGE
Bundler found mismatched checksums. This is a potential security risk.
#{@lock_name} #{@existing.to_lock}
from #{@existing.sources.join("\n and ")}
#{@lock_name} #{@checksum.to_lock}
from #{@checksum.sources.join("\n and ")}
#{mismatch_resolution_instructions}
To ignore checksum security warnings, disable checksum validation with
`bundle config set --local disable_checksum_validation true`
MESSAGE
end
def mismatch_resolution_instructions
removable, remote = [@existing, @checksum].partition(&:removable?)
case removable.size
when 0
msg = +"Mismatched checksums each have an authoritative source:\n"
msg << " 1. #{@existing.sources.reject(&:removable?).map(&:to_s).join(" and ")}\n"
msg << " 2. #{@checksum.sources.reject(&:removable?).map(&:to_s).join(" and ")}\n"
msg << "You may need to alter your Gemfile sources to resolve this issue.\n"
when 1
msg = +"If you trust #{remote.first.sources.first}, to resolve this issue you can:\n"
msg << removable.first.removal_instructions
when 2
msg = +"To resolve this issue you can either:\n"
msg << @checksum.removal_instructions
msg << "or if you are sure that the new checksum from #{@checksum.sources.first} is correct:\n"
msg << @existing.removal_instructions
end
end
status_code(37)
end
class PermissionError < BundlerError
def initialize(path, permission_type = :write)
@path = path
@permission_type = permission_type
end
def action
case @permission_type
when :read then "read from"
when :write then "write to"
when :executable, :exec then "execute"
else @permission_type.to_s
end
end
def permission_type
case @permission_type
when :create
"executable permissions for all parent directories and write permissions for `#{parent_folder}`"
else
"#{@permission_type} permissions for that path"
end
end
def parent_folder
File.dirname(@path)
end
def message
"There was an error while trying to #{action} `#{@path}`. " \
"It is likely that you need to grant #{permission_type}."
end
status_code(23)
end
class GemRequireError < BundlerError
attr_reader :orig_exception
def initialize(orig_exception, msg)
full_message = msg + "\nGem Load Error is: #{orig_exception.message}\n"\
"Backtrace for gem load error is:\n"\
"#{orig_exception.backtrace.join("\n")}\n"\
"Bundler Error Backtrace:\n"
super(full_message)
@orig_exception = orig_exception
end
status_code(24)
end
class YamlSyntaxError < BundlerError
attr_reader :orig_exception
def initialize(orig_exception, msg)
super(msg)
@orig_exception = orig_exception
end
status_code(25)
end
class TemporaryResourceError < PermissionError
def message
"There was an error while trying to #{action} `#{@path}`. " \
"Some resource was temporarily unavailable. It's suggested that you try" \
"the operation again."
end
status_code(26)
end
class VirtualProtocolError < BundlerError
def message
"There was an error relating to virtualization and file access. " \
"It is likely that you need to grant access to or mount some file system correctly."
end
status_code(27)
end
class OperationNotSupportedError < PermissionError
def message
"Attempting to #{action} `#{@path}` is unsupported by your OS."
end
status_code(28)
end
class NoSpaceOnDeviceError < PermissionError
def message
"There was an error while trying to #{action} `#{@path}`. " \
"There was insufficient space remaining on the device."
end
status_code(31)
end
class GenericSystemCallError < BundlerError
attr_reader :underlying_error
def initialize(underlying_error, message)
@underlying_error = underlying_error
super("#{message}\nThe underlying system error is #{@underlying_error.class}: #{@underlying_error}")
end
status_code(32)
end
class DirectoryRemovalError < BundlerError
def initialize(orig_exception, msg)
full_message = "#{msg}.\n" \
"The underlying error was #{orig_exception.class}: #{orig_exception.message}, with backtrace:\n" \
" #{orig_exception.backtrace.join("\n ")}\n\n" \
"Bundler Error Backtrace:"
super(full_message)
end
status_code(36)
end
class InsecureInstallPathError < BundlerError
def initialize(name, path)
@name = name
@path = path
end
def message
"Bundler cannot reinstall #{@name} because there's a previous installation of it at #{@path} that is unsafe to remove.\n" \
"The parent of #{@path} is world-writable and does not have the sticky bit set, making it insecure to remove due to potential vulnerabilities.\n" \
"Please change the permissions of #{File.dirname(@path)} or choose a different install path."
end
status_code(38)
end
class CorruptBundlerInstallError < BundlerError
def initialize(loaded_spec)
@loaded_spec = loaded_spec
end
def message
"The running version of Bundler (#{Bundler::VERSION}) does not match the version of the specification installed for it (#{@loaded_spec.version}). " \
"This can be caused by reinstalling Ruby without removing previous installation, leaving around an upgraded default version of Bundler. " \
"Reinstalling Ruby from scratch should fix the problem."
end
status_code(39)
end
class InvalidArgumentError < BundlerError; status_code(40); end
end